TCP - C语言实现详解
2022/8/8 6:22:55
本文主要是介绍TCP - C语言实现详解,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
原文网址:https://www.jianshu.com/p/7c7fe00d01b1
TCP - C语言实现详解
chiwin 2020.06.09 17:17:46字数 616阅读 589 tcp-三次握手-四次挥手.jpg【一】服务端
1、创建一个socket,拿到tcp的一个文件描述符
int serverSocket = socket(AF_INET, SOCK_STREAM, 0)
其中,第一个参数表示地址类型,AF_INET为IPV4,AF_INET6可支持IPV6;第二个参数表明是TCP【面向连接的稳定数据传输SOCK_STREAM】连接;第三个参数默认0
2、申请一个服务端结构体并初始化
struct sockaddr_in server_addr; // 结构体 bzero(&server_addr, sizeof(server_addr)); // 判空 server_addr.sin_family = AF_INET; server_addr.sin_port = htons(SERVER_PORT); //server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 本机通信,INADDR_ANY代表0.0.0.0:所有地址 server_addr.sin_addr.s_addr = inet_addr("186.66.66.66"); //非本机通信
申请了结构体sockaddr_in,将协议族设置为ipv4,传入了port,设置了ip
3、绑定socket和结构体ipport
bind(serverSocket, (struct sockaddr *)&server_addr, sizeof(server_addr)
返回小于0为失败,否则绑定成功
注:
sockaddr在头文件#include <sys/socket.h>
中定义,sockaddr的缺陷是:sa_data把目标地址和端口信息混在一起了
sockaddr_in在头文件#include<netinet/in.h>或#include <arpa/inet.h>
中定义,该结构体解决了sockaddr的缺陷,把port和addr 分开储存在两个变量中
4、监听socket句柄
listen(serverSocket, 5)
返回小于0为失败,否则监听成功
5、为客户端连接做准备,构造结构体
struct sockaddr_in client_addr; int addr_len = sizeof(client_addr);
6、等待客户端连接
int clientSocket = accept(serverSocket, (struct sockaddr*)&client_addr, (socklen_t*)&addr_len);
返回小于0为失败,否则接受客户端连接
注:
accept接收服务端的链接请求,并返回一个客户端的socket句柄,参数一为服务端socket句柄,参数二为客户端结构体,参数三位客户端结构体长度,其中参数二和参数三为出参,可以解析到客户端的信息,如下:
printf("Client IP %s\n", inet_ntoa(client_addr.sin_addr)); printf("Client Port %d\n", htons(client_addr.sin_port));
7、客户端连接后,接受msg消息
char buffer[200]; int dataLen = recv(clientSocket , buffer, 1024, 0); buffer[dataLen] = '\0';
dateLen为接受到的data的大小,buffer为收到的消息
8、服务端收完消息后,回给客户端相同的消息【或者自己写消息到buffer里并发送】
send(clientSocket , buffer, dataLen , 0);
9、继续监听客户端连接请求
使用while循环返回到accept函数
【二】客户端
1、创建一个socket,拿到tcp的一个文件描述符
int clientSocket = socket(AF_INET, SOCK_STREAM, 0);
2、申请一个服务端结构体并填充服务端信息
struct sockaddr_in serverAddr; serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(SERVER_PORT); //指定服务器端的ip,本地测试:127.0.0.1 //inet_addr()函数,将点分十进制IP转换成网络字节序IP serverAddr.sin_addr.s_addr = inet_addr("186.66.66.66");
3、尝试连接服务端
connect(clientSocket, (struct sockaddr *)&serverAddr, sizeof(serverAddr))
返回小于0为失败,否则连接成功
4、开始发送数据并在发送成功后接收服务端返回的数据
char sendbuf[200] = {"sendMsg to server"}; char recvbuf[200] = {0}; send(clientSocket, sendbuf, strlen(sendbuf), 0); int recvData = recv(clientSocket, recvbuf, 200, 0); recvbuf[recvData] = '\0';
5、完成数据通信后关闭连接
close(clientSocket);
【三】代码
参考:https://blog.csdn.net/lovekun1989/article/details/41042273
服务端代码如下:
/*socket tcp服务器端*/ #include <sys/stat.h> #include <fcntl.h> #include <errno.h> #include <netdb.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #define SERVER_PORT 12345 /* 监听后,一直处于accept阻塞状态, 直到有客户端连接, 当客户端输入quit后,客户端主动close与服务端的链接 */ int main() { int serverSocket; struct sockaddr_in server_addr; struct sockaddr_in client_addr; int addr_len = sizeof(client_addr); int clientSocket ; char buffer[200]; int dataLen; if((serverSocket = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("socket"); return 1; } bzero(&server_addr, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(SERVER_PORT); //本机通信 //server_addr.sin_addr.s_addr = htonl(INADDR_ANY); //非本机通信 server_addr.sin_addr.s_addr = inet_addr("186.66.66.66"); //对于bind,accept之类的函数,里面套接字参数都是需要强制转换成(struct sockaddr *) if(bind(serverSocket, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { perror("connect"); return 1; } //设置服务器上的socket为监听状态 if(listen(serverSocket, 5) < 0) { perror("listen"); return 1; } while(1) { printf("Listening on port: %d\n", SERVER_PORT); //调用accept函数后,会进入阻塞状态 //accept返回一个套接字的文件描述符,这样服务器端便有两个套接字的文件描述符, //serverSocket和clientSocket 。 //serverSocket仍然继续在监听状态,clientSocket 则负责接收和发送数据 //client_addr是一个传出参数,accept返回时,传出客户端的地址和端口号 //addr_len是一个传入-传出参数,传入的是调用者提供的缓冲区的client_addr的长度,以避免缓冲区溢出。 //传出的是客户端地址结构体的实际长度。 //出错返回-1 clientSocket = accept(serverSocket, (struct sockaddr*)&client_addr, (socklen_t*)&addr_len); if(clientSocket < 0) { perror("accept"); continue; } printf("\nrecv clientSocket data...n"); //inet_ntoa ip地址转换函数,将网络字节序IP转换为点分十进制IP //表达式:char *inet_ntoa (struct in_addr); printf("IP is %s\n", inet_ntoa(client_addr.sin_addr)); printf("Port is %d\n", htons(client_addr.sin_port)); while(1) { dataLen = recv(clientSocket , buffer, 1024, 0); if(dataLen < 0) { perror("recv"); continue; } buffer[dataLen] = '\0'; if(strcmp(buffer, "quit") == 0) break; printf("%drecv data is %s\n", dataLen, buffer); send(clientSocket , buffer, dataLen, 0); } } return 0; }
客户端代码如下:
/*socket tcp客户端*/ #include <sys/stat.h> #include <fcntl.h> #include <errno.h> #include <netdb.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #define SERVER_PORT 12345 /* 连接到服务器后,会不停循环,等待输入, 输入quit后,断开与服务器的连接 */ int main() { //客户端只需要一个套接字文件描述符,用于和服务器通信 int clientSocket; //描述服务器的socket struct sockaddr_in serverAddr; char sendbuf[200]; char recvbuf[200]; int recvData; if((clientSocket = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("socket"); return 1; } serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(SERVER_PORT); //指定服务器端的ip,本地测试:127.0.0.1 //inet_addr()函数,将点分十进制IP转换成网络字节序IP serverAddr.sin_addr.s_addr = inet_addr("186.66.66.66"); if(connect(clientSocket, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) < 0) { perror("connect"); return 1; } printf("connect with destination host...\n"); while(1) { printf("Input your world:>"); scanf("%s", sendbuf); printf("\n"); send(clientSocket, sendbuf, strlen(sendbuf), 0); if(strcmp(sendbuf, "quit") == 0) break; recvData = recv(clientSocket, recvbuf, 200, 0); recvbuf[recvData] = '\0'; printf("recv data of my world is: %s\n", recvbuf); } close(clientSocket); return 0; }
这篇关于TCP - C语言实现详解的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-22怎么实现ansible playbook 备份代码中命名包含时间戳功能?-icode9专业技术文章分享
- 2024-11-22ansible 的archive 参数是什么意思?-icode9专业技术文章分享
- 2024-11-22ansible 中怎么只用archive 排除某个目录?-icode9专业技术文章分享
- 2024-11-22exclude_path参数是什么作用?-icode9专业技术文章分享
- 2024-11-22微信开放平台第三方平台什么时候调用数据预拉取和数据周期性更新接口?-icode9专业技术文章分享
- 2024-11-22uniapp 实现聊天消息会话的列表功能怎么实现?-icode9专业技术文章分享
- 2024-11-22在Mac系统上将图片中的文字提取出来有哪些方法?-icode9专业技术文章分享
- 2024-11-22excel 表格中怎么固定一行显示不滚动?-icode9专业技术文章分享
- 2024-11-22怎么将 -rwxr-xr-x 修改为 drwxr-xr-x?-icode9专业技术文章分享
- 2024-11-22在Excel中怎么将小数向上取整到最接近的整数?-icode9专业技术文章分享