socket编程实例-TCP
TCP通信流程
![](https://img2022.cnblogs.com/blog/1704997/202206/1704997-20220629164425036-886481535.png)
// TCP 通信的流程 // 服务器端 (被动接受连接的角色)
1. 创建一个用于监听的套接字 - 监听:监听有客户端的连接 - 套接字:这个套接字其实就是一个文件描述符 2. 将这个监听文件描述符和本地的IP和端口绑定(IP和端口就是服务器的地址信息) - 客户端连接服务器的时候使用的就是这个IP和端口 3. 设置监听,监听的fd开始工作 4. 阻塞等待,当有客户端发起连接,解除阻塞,接受客户端的连接,会得到一个和客户端通信的套接字 (fd) 5. 通信 - 接收数据 - 发送数据
6. 通信结束,断开连接 // 客户端 1. 创建一个用于通信的套接字(fd) 2. 连接服务器,需要指定连接的服务器的 IP 和 端口
3. 连接成功了,客户端可以直接和服务器通信 - 接收数据 - 发送数据 4. 通信结束,断开连接
简单例子
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
// TCP通信的客户端 #include <stdio.h> #include <arpa/inet.h> #include <unistd.h> #include <string.h> #include <stdlib.h> int main() { // 1.创建套接字 int fd = socket(AF_INET, SOCK_STREAM, 0); if(fd == -1) { perror("socket"); exit(-1); } // 2.连接服务器端 struct sockaddr_in serveraddr; serveraddr.sin_family = AF_INET; inet_pton(AF_INET, "10.82.220.132", &serveraddr.sin_addr.s_addr); serveraddr.sin_port = htons(9999); int ret = connect(fd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)); if(ret == -1) { perror("connect"); exit(-1); } // 3. 通信 char recvBuf[1024] = {0}; while(1) { char * data = "hello,i am client"; // 给客户端发送数据 write(fd, data , strlen(data)); sleep(1); int len = read(fd, recvBuf, sizeof(recvBuf)); if(len == -1) { perror("read"); exit(-1); } else if(len > 0) { printf("recv server data : %s\n", recvBuf); } else if(len == 0) { // 表示服务器端断开连接 printf("server closed..."); break; } } // 关闭连接 close(fd); return 0; }
recv server data : hello,i am server recv server data : hello,i am server recv server data : hello,i am server recv server data : hello,i am server recv server data : hello,i am server recv server data : hello,i am server recv server data : hello,i am server recv server data : hello,i am server recv server data : hello,i am server recv server data : hello,i am server
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
// TCP 通信的服务器端 #include <stdio.h> #include <arpa/inet.h> #include <unistd.h> #include <string.h> #include <stdlib.h> int main() { // 1.创建socket(用于监听的套接字) int lfd = socket(AF_INET, SOCK_STREAM, 0); if(lfd == -1) { perror("socket"); exit(-1); } // 2.绑定 struct sockaddr_in saddr; saddr.sin_family = AF_INET; // inet_pton(AF_INET, "192.168.193.128", saddr.sin_addr.s_addr); saddr.sin_addr.s_addr = INADDR_ANY; // 0.0.0.0 saddr.sin_port = htons(9999); int ret = bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr)); if(ret == -1) { perror("bind"); exit(-1); } // 3.监听 ret = listen(lfd, 8); if(ret == -1) { perror("listen"); exit(-1); } // 4.接收客户端连接 struct sockaddr_in clientaddr; int len = sizeof(clientaddr); int cfd = accept(lfd, (struct sockaddr *)&clientaddr, &len); if(cfd == -1) { perror("accept"); exit(-1); } // 输出客户端的信息 char clientIP[16]; inet_ntop(AF_INET, &clientaddr.sin_addr.s_addr, clientIP, sizeof(clientIP)); unsigned short clientPort = ntohs(clientaddr.sin_port); printf("client ip is %s, port is %d\n", clientIP, clientPort); // 5.通信 char recvBuf[1024] = {0}; while(1) { // 获取客户端的数据 int num = read(cfd, recvBuf, sizeof(recvBuf)); if(num == -1) { perror("read"); exit(-1); } else if(num > 0) { printf("recv client data : %s\n", recvBuf); } else if(num == 0) { // 表示客户端断开连接 printf("clinet closed..."); break; } char * data = "hello,i am server"; // 给客户端发送数据 write(cfd, data, strlen(data)); } // 关闭文件描述符 close(cfd); close(lfd); return 0; }
client ip is 10.82.220.132, port is 58556 recv client data : hello,i am client recv client data : hello,i am client recv client data : hello,i am client recv client data : hello,i am client recv client data : hello,i am client recv client data : hello,i am client recv client data : hello,i am client recv client data : hello,i am client
多进程版服务端
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <stdio.h> #include <arpa/inet.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <signal.h> #include <wait.h> #include <errno.h> void recyleChild(int arg) { while(1) { int ret = waitpid(-1, NULL, WNOHANG); if(ret == -1) { // 所有的子进程都回收了 break; }else if(ret == 0) { // 还有子进程活着 break; } else if(ret > 0){ // 被回收了 printf("子进程 %d 被回收了\n", ret); } } } int main() { struct sigaction act; act.sa_flags = 0; sigemptyset(&act.sa_mask); act.sa_handler = recyleChild; // 注册信号捕捉 sigaction(SIGCHLD, &act, NULL); // 创建socket int lfd = socket(PF_INET, SOCK_STREAM, 0); if(lfd == -1){ perror("socket"); exit(-1); } struct sockaddr_in saddr; saddr.sin_family = AF_INET; saddr.sin_port = htons(9999); saddr.sin_addr.s_addr = INADDR_ANY; // 绑定 int ret = bind(lfd,(struct sockaddr *)&saddr, sizeof(saddr)); if(ret == -1) { perror("bind"); exit(-1); } // 监听 ret = listen(lfd, 128); if(ret == -1) { perror("listen"); exit(-1); } // 不断循环等待客户端连接 while(1) { struct sockaddr_in cliaddr; int len = sizeof(cliaddr); // 接受连接 int cfd = accept(lfd, (struct sockaddr*)&cliaddr, &len); if(cfd == -1) { if(errno == EINTR) { continue; } perror("accept"); exit(-1); } // 每一个连接进来,创建一个子进程跟客户端通信 pid_t pid = fork(); if(pid == 0) { // 子进程 // 获取客户端的信息 char cliIp[16]; inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, cliIp, sizeof(cliIp)); unsigned short cliPort = ntohs(cliaddr.sin_port); printf("client ip is : %s, prot is %d\n", cliIp, cliPort); // 接收客户端发来的数据 char recvBuf[1024]; while(1) { int len = read(cfd, &recvBuf, sizeof(recvBuf)); if(len == -1) { perror("read"); exit(-1); }else if(len > 0) { printf("recv client : %s\n", recvBuf); } else if(len == 0) { printf("client closed....\n"); break; } write(cfd, recvBuf, strlen(recvBuf) + 1); } close(cfd); exit(0); // 退出当前子进程 } } close(lfd); return 0; }
多线程版服务端
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <stdio.h> #include <arpa/inet.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <pthread.h> struct sockInfo { int fd; // 通信的文件描述符 struct sockaddr_in addr; pthread_t tid; // 线程号 }; struct sockInfo sockinfos[128]; void * working(void * arg) { // 子线程和客户端通信 cfd 客户端的信息 线程号 // 获取客户端的信息 struct sockInfo * pinfo = (struct sockInfo *)arg; char cliIp[16]; inet_ntop(AF_INET, &pinfo->addr.sin_addr.s_addr, cliIp, sizeof(cliIp)); unsigned short cliPort = ntohs(pinfo->addr.sin_port); printf("client ip is : %s, prot is %d\n", cliIp, cliPort); // 接收客户端发来的数据 char recvBuf[1024]; while(1) { int len = read(pinfo->fd, &recvBuf, sizeof(recvBuf)); if(len == -1) { perror("read"); exit(-1); }else if(len > 0) { printf("recv client : %s\n", recvBuf); } else if(len == 0) { printf("client closed....\n"); break; } write(pinfo->fd, recvBuf, strlen(recvBuf) + 1); } close(pinfo->fd); return NULL; } int main() { // 创建socket int lfd = socket(PF_INET, SOCK_STREAM, 0); if(lfd == -1){ perror("socket"); exit(-1); } struct sockaddr_in saddr; saddr.sin_family = AF_INET; saddr.sin_port = htons(9998); saddr.sin_addr.s_addr = INADDR_ANY; // 绑定 int ret = bind(lfd,(struct sockaddr *)&saddr, sizeof(saddr)); if(ret == -1) { perror("bind"); exit(-1); } // 监听 ret = listen(lfd, 128); if(ret == -1) { perror("listen"); exit(-1); } // 初始化数据 int max = sizeof(sockinfos) / sizeof(sockinfos[0]); for(int i = 0; i < max; i++) { bzero(&sockinfos[i], sizeof(sockinfos[i])); sockinfos[i].fd = -1; sockinfos[i].tid = -1; } // 循环等待客户端连接,一旦一个客户端连接进来,就创建一个子线程进行通信 while(1) { struct sockaddr_in cliaddr; int len = sizeof(cliaddr); // 接受连接 int cfd = accept(lfd, (struct sockaddr*)&cliaddr, &len); struct sockInfo * pinfo; for(int i = 0; i < max; i++) { // 从这个数组中找到一个可以用的sockInfo元素 if(sockinfos[i].fd == -1) { pinfo = &sockinfos[i]; break; } if(i == max - 1) { // 只允许128个线程 sleep(1); i--; } } pinfo->fd = cfd; memcpy(&pinfo->addr, &cliaddr, len); // 创建子线程 pthread_create(&pinfo->tid, NULL, working, pinfo); pthread_detach(pinfo->tid); } close(lfd); return 0; }
第一版:实现简单功能
客户端从标准输入读入字符串传给服务端,服务端把字符串中的小写字母转化为大写字母传回客户端
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <stdio.h> #include <unistd.h> #include <sys/socket.h> #include <stdlib.h> #include <arpa/inet.h> #include <ctype.h> #include <string.h> #define SERV_IP "127.0.0.1" #define SERV_PORT 6666 int main() { int cfd; struct sockaddr_in serv_addr; socklen_t serv_addr_len; char buf[BUFSIZ]; int n; cfd = socket(AF_INET, SOCK_STREAM, 0); memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(SERV_PORT); //serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); inet_pton(AF_INET, SERV_IP, &serv_addr.sin_addr.s_addr); //客户端可以不用bind()绑定ip和端口号,操作系会默认分配 connect(cfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); while(1) { fgets(buf, sizeof(buf), stdin); write(cfd, buf, strlen(buf)); n = read(cfd, buf, sizeof(buf)); write(STDOUT_FILENO, buf, n); } close(cfd); return 0; }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <stdio.h> #include <unistd.h> #include <sys/socket.h> #include <stdlib.h> #include <arpa/inet.h> #include <ctype.h> #define SERV_PORT 6666 int main() { int sfd, cfd; struct sockaddr_in serv_addr, clie_addr; socklen_t clie_addr_len; char buf[BUFSIZ]; int n, i; sfd = socket(AF_INET, SOCK_STREAM, 0); serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(SERV_PORT); serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); bind(sfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); listen(sfd, 128); clie_addr_len = sizeof(clie_addr); cfd = accept(sfd, (struct sockaddr*)&clie_addr, &clie_addr_len); while(1) { n = read(cfd, buf, sizeof(buf)); for(i=0; i<n; i++) { buf[i] = toupper(buf[i]); } write(cfd, buf, n); } close(sfd); close(cfd); return 0; }
第二版:加入错误封装
上面没有对错误进行处理,我们知道,系统调用不能保证每次都成功,必须进行出错处理,这样一方面可以保证程序逻辑正常,另一方面可以迅速得到故障信息。为使错误处理的代码不影响主程序的可读性,我们把与socket相关的一些系统函数加上错误处理代码包装成新的函数(新函数和原函数调用的区别仅仅在于新函数名首字母大写了),做成一个模块wrap.c,这样在调用socket函数时,调用我们封装了错误处理的函数就可以了。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <stdlib.h> #include <errno.h> #include <sys/socket.h> void perr_exit(const char *s) { perror(s); exit(1); } int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr) { int n; again: if ( (n = accept(fd, sa, salenptr)) < 0) { if ((errno == ECONNABORTED) || (errno == EINTR)) goto again; else perr_exit("accept error"); } return n; } int Bind(int fd, const struct sockaddr *sa, socklen_t salen) { int n; if ((n = bind(fd, sa, salen)) < 0) perr_exit("bind error"); return n; } int Connect(int fd, const struct sockaddr *sa, socklen_t salen) { int n; if ((n = connect(fd, sa, salen)) < 0) perr_exit("connect error"); return n; } int Listen(int fd, int backlog) { int n; if ((n = listen(fd, backlog)) < 0) perr_exit("listen error"); return n; } int Socket(int family, int type, int protocol) { int n; if ( (n = socket(family, type, protocol)) < 0) perr_exit("socket error"); return n; } ssize_t Read(int fd, void *ptr, size_t nbytes) { ssize_t n; again: if ( (n = read(fd, ptr, nbytes)) == -1) { if (errno == EINTR) goto again; else return -1; } return n; } ssize_t Write(int fd, const void *ptr, size_t nbytes) { ssize_t n; again: if ( (n = write(fd, ptr, nbytes)) == -1) { if (errno == EINTR) goto again; else return -1; } return n; } int Close(int fd) { int n; if ((n = close(fd)) == -1) perr_exit("close error"); return n; } ssize_t Readn(int fd, void *vptr, size_t n) { size_t nleft; ssize_t nread; char *ptr; ptr = vptr; nleft = n; while (nleft > 0) { if ( (nread = read(fd, ptr, nleft)) < 0) { if (errno == EINTR) nread = 0; else return -1; } else if (nread == 0) break; nleft -= nread; ptr += nread; } return n - nleft; } ssize_t Writen(int fd, const void *vptr, size_t n) { size_t nleft; ssize_t nwritten; const char *ptr; ptr = vptr; nleft = n; while (nleft > 0) { if ( (nwritten = write(fd, ptr, nleft)) <= 0) { if (nwritten < 0 && errno == EINTR) nwritten = 0; else return -1; } nleft -= nwritten; ptr += nwritten; } return n; } static ssize_t my_read(int fd, char *ptr) { static int read_cnt; static char *read_ptr; static char read_buf[100]; if (read_cnt <= 0) { again: if ((read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) { if (errno == EINTR) goto again; return -1; } else if (read_cnt == 0) return 0; read_ptr = read_buf; } read_cnt--; *ptr = *read_ptr++; return 1; } ssize_t Readline(int fd, void *vptr, size_t maxlen) { ssize_t n, rc; char c, *ptr; ptr = vptr; for (n = 1; n < maxlen; n++) { if ( (rc = my_read(fd, &c)) == 1) { *ptr++ = c; if (c == '\n') break; } else if (rc == 0) { *ptr = 0; return n - 1; } else return -1; } *ptr = 0; return n; }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#ifndef __WRAP_H_ #define __WRAP_H_ void perr_exit(const char *s); int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr); int Bind(int fd, const struct sockaddr *sa, socklen_t salen); int Connect(int fd, const struct sockaddr *sa, socklen_t salen); int Listen(int fd, int backlog); int Socket(int family, int type, int protocol); ssize_t Read(int fd, void *ptr, size_t nbytes); ssize_t Write(int fd, const void *ptr, size_t nbytes); int Close(int fd); ssize_t Readn(int fd, void *vptr, size_t n); ssize_t Writen(int fd, const void *vptr, size_t n); ssize_t my_read(int fd, char *ptr); ssize_t Readline(int fd, void *vptr, size_t maxlen); #endif
第三版:使用多进程技术在服务端实现高并发
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <stdio.h> #include <unistd.h> #include <sys/socket.h> #include <stdlib.h> #include <arpa/inet.h> #include <ctype.h> #include <strings.h> #include <sys/wait.h> #include "wrap.h" #define SERV_PORT 6666 void wait_child(int signo) { while(waitpid(0, NULL, WNOHANG) > 0); return ; } int main() { pid_t pid; int sfd, cfd; struct sockaddr_in serv_addr, clie_addr; socklen_t clie_addr_len; char buf[BUFSIZ], clie_IP[BUFSIZ]; int n, i; sfd = Socket(AF_INET, SOCK_STREAM, 0); bzero(&serv_addr, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(SERV_PORT); serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); Bind(sfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); Listen(sfd, 128); while(1) { clie_addr_len = sizeof(clie_addr); cfd = Accept(sfd, (struct sockaddr*)&clie_addr, &clie_addr_len); printf("client IP: %s, port: %d\n", inet_ntop(AF_INET, &clie_addr.sin_addr.s_addr, clie_IP, sizeof(clie_IP)), ntohs(clie_addr.sin_port)); pid = fork(); if(pid < 0) { perror("fork error"); exit(1); } else if(pid == 0) { close(sfd); break; } else { close(cfd); signal(SIGCHLD, wait_child); } } if(pid == 0) { while(1) { n = Read(cfd, buf, sizeof(buf)); if(n == 0) { close(cfd); return 0; } else if(n == -1) { perror("read error"); exit(1); } else { for(i=0; i<n; i++) { buf[i] = toupper(buf[i]); } write(cfd, buf, n); write(STDOUT_FILENO, buf, n); } } } return 0; }
第四版:使用多线程技术在服务端实现高并发
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <stdio.h> #include <unistd.h> #include <sys/socket.h> #include <stdlib.h> #include <arpa/inet.h> #include <ctype.h> #include <strings.h> #include <sys/wait.h> #include <fcntl.h> #include <pthread.h> #include <string.h> #include "wrap.h" #define SERV_PORT 6666 #define MAXLINE 8192 struct s_info{ struct sockaddr_in cliaddr; int connfd; }; void* do_work(void *arg) { int n,i; struct s_info *ts = (struct s_info*)arg; char buf[MAXLINE]; char str[INET_ADDRSTRLEN]; while(1){ n = Read(ts->connfd, buf, MAXLINE); if(n == 0){ printf("the client %d closed...\n", ts->connfd); break; } printf("received from %s at PORT %d\n", inet_ntop(AF_INET, &(*ts).cliaddr.sin_addr, str, sizeof(str)), ntohs((*ts).cliaddr.sin_port)); for(i=0; i<n; i++){ buf[i] = toupper(buf[i]); } write(ts->connfd, buf, n); write(STDOUT_FILENO, buf, n); } Close(ts->connfd); return (void *)0; } int main() { int sfd, cfd; struct sockaddr_in serv_addr, clie_addr; socklen_t clie_addr_len; struct s_info ts[256]; pthread_t tid; int i = 0; sfd = Socket(AF_INET, SOCK_STREAM, 0); bzero(&serv_addr, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(SERV_PORT); serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); Bind(sfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); Listen(sfd, 128); while(1) { clie_addr_len = sizeof(clie_addr); cfd = Accept(sfd, (struct sockaddr*)&clie_addr, &clie_addr_len); ts[i].cliaddr = clie_addr; ts[i].connfd = cfd; pthread_create(&tid, NULL, do_work, (void*)&ts[i]); pthread_detach(tid); i++; } return 0; }