单线程 TCP/IP 服务器和客户端的实现

打印 上一主题 下一主题

主题 889|帖子 889|积分 2667

单线程 TCP/IP 服务器和客户端的实现


  
参考课程
通信流程

服务端


  • socket:创建监听的文件描述符(socket) fd;
  • bind:fd 和自身的 ip 和端口绑定;
  • listen:为当前文件描述符设置监听,主要是设置客户端毗连数目;
  • accept:阻塞直到客户端请求毗连;

    • 若收到客户端请求,那么将会创建一个新的 socket,即用作通信的文件描述符 cfd;此后即可利用 cfd 进行通信;

  • recv:接收来自客户端的数据;
  • send:向客户端发生数据;
  • 结束毗连;
客户端


  • socket:建立通信的文件描述符 fd;留意此处的 fd 和服务器是不同的,该处的 fd 直接用于通信,而服务器中的第一个是用于监听;
  • connect:设置好服务器的 ip 和端口后利用 connect 建立毗连;
  • send:将数据发送到服务器;
  • recv:收到服务器发送的数据;
  • close:结束毗连;

代码实现

服务端



  • socket:创建监听的文件描述符(socket) fd;
  1. // 1. 创建 socket 文件描述符,使用 ipv4 流式协议 TCP
  2. int fd = socket(AF_INET, SOCK_STREAM, 0);
  3. if (fd == -1) {
  4.     perror("socket fail...!\n");
  5.     exit(0);
  6. }
复制代码


  • bind:fd 和自身的 ip 和端口绑定;
此处首先利用 sockaddr_in 设置 ip 和端口。需要指定协议簇为 IPv4(AF_INET),端口为 9898,留意此处的 9898 在主机上的存储方式为小端,需要将其转化为网络中的存储方式即大端。因此利用 htons 将主机上的端口转化为网络中的端口;
bind(fd, (struct sockaddr*)&in_addr, sizeof(in_addr)); 是将监听的文件描述符 fd,和本机的 ip 和端口进行绑定;
  1. // 2. socket 和本机ip 端口绑定
  2. struct sockaddr_in in_addr;
  3. in_addr.sin_family = AF_INET;
  4. // 端口号,短整型,主机转网络
  5. in_addr.sin_port = htons(9898);
  6. // 获取本机 ip 地址
  7. in_addr.sin_addr.s_addr = INADDR_ANY;
  8. int ret = bind(fd, (struct sockaddr*)&in_addr, sizeof(in_addr));
  9. if (ret == -1) {
  10.     perror("bind fail...\n");
  11.     exit(0);
  12. }
复制代码


  • listen:为当前文件描述符设置监听,主要是设置客户端毗连数目;
  1. // 3. 监听客户端
  2. ret = listen(fd, 128);
  3. if (ret == -1) {
  4.     perror("listen fail...\n");
  5.     exit(0);
  6. }
复制代码


  • accept:阻塞直到客户端请求毗连;

    • 若收到客户端请求,那么将会创建一个新的 socket,即用作通信的文件描述符 cfd;此后即可利用 cfd 进行通信;

int cfd = accept(fd, (struct sockaddr*)&cli_addr, &cli_len); 利用 fd 监听客户端请求,若收到请求,将客户端的 ip 和端口信息写入 cli_addr,并创建一个用于通信的文件描述符(socket) cfd;在收到后,打印客户端信息;
此处涉及到 ip 所在的转换;ip 所在的转换有两种需求:


  • 主机给定一个 ip 字符串转换为网络字节序;这是在客户端代码中利用,在后续代码中利用;将字符串 src 转为字节序的 ip 所在存入 dst 中;
  1. int inet_pton(int af, const char *src, void *dst);
复制代码


  • 将网络中的字节序转换为字符串;即在本处利用;将字节序的 ip 所在转换为字符串存入 dst 中;
  1. const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
复制代码
  1. // 4. 阻塞等待客户端连接
  2. struct sockaddr_in cli_addr;
  3. int cli_len = sizeof(cli_addr);
  4. int cfd = accept(fd, (struct sockaddr*)&cli_addr, &cli_len);
  5. if (cfd == -1) {
  6.     perror("accept fail...\n");
  7.     exit(0);
  8. }
  9. // 打印客户端信息
  10. char ip[20] = {0};
  11. printf("客户端 IP 地址为:%s,端口号为 %d\n",
  12.         inet_ntop(AF_INET, &cli_addr.sin_addr.s_addr, ip, sizeof(ip)),
  13.                ntohs(cli_addr.sin_port));
复制代码


  • recv:接收来自客户端的数据;
  • send:向客户端发生数据;
界说缓冲区 buff 用于存放接收到的数据和放置需要发送的数据;留意此处是利用通信文件描述符 cfd 用于通信;recv(cfd, buff, sizeof(buff), 0) 利用套接字 cfd 进行通信,将接收到的数据放入 buff 中;
send(cfd, buff, size, 0); 是将需要发送的数据放入 buff 中进行发送;
  1. // 接收数据的缓冲区
  2. char buff[1024];
  3. memset(buff, 0, sizeof(buff));
  4. int size = recv(cfd, buff, sizeof(buff), 0);
  5. if (size > 0) {
  6.     printf("client say: %s\n", buff);
  7.     send(cfd, buff, size, 0);
  8. } else if (size == 0) {
  9.     printf("客户端断开了连接...\n");
  10.     break;
  11. } else {
  12.     perror("read fail...\n");
  13.     break;
  14. }
复制代码


  • 结束毗连;
  1. close(fd);
  2. close(cfd);
复制代码
团体代码:
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. #include <string.h>
  5. #include <arpa/inet.h>
  6. int main () {
  7.     // 1. 创建 socket 文件描述符,使用 ipv4 流式协议 TCP
  8.     int fd = socket(AF_INET, SOCK_STREAM, 0);
  9.     if (fd == -1) {
  10.         perror("socket fail...!\n");
  11.         exit(0);
  12.     }
  13.     // 2. socket 和本机ip 端口绑定
  14.     struct sockaddr_in in_addr;
  15.     in_addr.sin_family = AF_INET;
  16.     // 端口号,短整型,主机转网络
  17.     in_addr.sin_port = htons(9898);
  18.     // 获取本机 ip 地址
  19.     in_addr.sin_addr.s_addr = INADDR_ANY;
  20.     int ret = bind(fd, (struct sockaddr*)&in_addr, sizeof(in_addr));
  21.     if (ret == -1) {
  22.         perror("bind fail...\n");
  23.         exit(0);
  24.     }
  25.     // 3. 监听客户端
  26.     ret = listen(fd, 128);
  27.     if (ret == -1) {
  28.         perror("listen fail...\n");
  29.         exit(0);
  30.     }
  31.     // 4. 阻塞等待客户端连接
  32.     struct sockaddr_in cli_addr;
  33.     int cli_len = sizeof(cli_addr);
  34.     int cfd = accept(fd, (struct sockaddr*)&cli_addr, &cli_len);
  35.     if (cfd == -1) {
  36.         perror("accept fail...\n");
  37.         exit(0);
  38.     }
  39.     // 打印客户端信息
  40.     char ip[20] = {0};
  41.     printf("客户端 IP 地址为:%s,端口号为 %d\n",
  42.             inet_ntop(AF_INET, &cli_addr.sin_addr.s_addr, ip, sizeof(ip)), ntohs(cli_addr.sin_port));
  43.    
  44.     // 5. 和客户端通信
  45.     while (1) {
  46.         // 接收数据的缓冲区
  47.         char buff[1024];
  48.         memset(buff, 0, sizeof(buff));
  49.         int size = recv(cfd, buff, sizeof(buff), 0);
  50.         if (size > 0) {
  51.             printf("client say: %s\n", buff);
  52.             send(cfd, buff, size, 0);
  53.         } else if (size == 0) {
  54.             printf("客户端断开了连接...\n");
  55.             break;
  56.         } else {
  57.             perror("read fail...\n");
  58.             break;
  59.         }
  60.     }
  61.     close(fd);
  62.     close(cfd);
  63.     return 0;
  64. }
复制代码
客户端



  • socket:建立通信的文件描述符 fd;留意此处的 fd 和服务器是不同的,该处的 fd 直接用于通信,而服务器中的第一个是用于监听;
fd 直接用于通信;
  1. // 1. 创建 socket 文件描述符,使用 ipv4 流式协议 TCP
  2. int fd = socket(AF_INET, SOCK_STREAM, 0);
  3. if (fd == -1) {
  4.     perror("socket fail...!\n");
  5.     exit(0);
  6. }
复制代码


  • connect:设置好服务器的 ip 和端口后利用 connect 建立毗连;
指定服务器所在,并将字符串转换为网络中的字节序 ip;利用 fd 与服务器建立毗连;服务器全部信息由 in_addr 给定;
  1. struct sockaddr_in in_addr;
  2. in_addr.sin_family = AF_INET;
  3. inet_pton(AF_INET, "127.0.0.1", &in_addr.sin_addr.s_addr);
  4. in_addr.sin_port = htons(9898);
  5. int ret = connect(fd, (struct sockaddr*)&in_addr, sizeof(in_addr));
  6. if (ret == -1) {
  7.     perror("connet fail...\n");
  8.     exit(0);
  9. }
复制代码


  • send:将数据发送到服务器;
  • recv:收到服务器发送的数据;
此处和服务器一致,不再赘述;
  1. char buff[1024];
  2. sprintf(buff, "你好服务器...%d\n", number++);
  3. send(fd, buff, strlen(buff) + 1, 0);
  4. memset(buff, 0, sizeof(buff));
  5. int size = recv(fd, buff, sizeof(buff), 0);
  6. if (size > 0) {
  7.     printf("server say: %s\n", buff);
  8. } else if(size == 0) {
  9.     printf("服务器断开连接\n");
  10.     break;
  11. } else {
  12.     perror("read fail...\n");
  13.     break;
  14. }
  15. sleep(1);
复制代码


  • close:结束毗连;
  1. close(fd);
复制代码
团体代码:
  1. #include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <string.h>#include <arpa/inet.h>int main () {    // 1. 创建 socket 文件描述符,利用 ipv4 流式协议 TCP    int fd = socket(AF_INET, SOCK_STREAM, 0);    if (fd == -1) {        perror("socket fail...!\n");        exit(0);    }        struct sockaddr_in in_addr;    in_addr.sin_family = AF_INET;    inet_pton(AF_INET, "124.223.59.159", &in_addr.sin_addr.s_addr);    in_addr.sin_port = htons(9898);    int ret = connect(fd, (struct sockaddr*)&in_addr, sizeof(in_addr));    if (ret == -1) {        perror("connet fail...\n");        exit(0);    }    // 3. 和服务端通信    int number = 0;    while (1) {        // 接收数据的缓冲区        char buff[1024];        sprintf(buff, "你好服务器...%d\n", number++);        send(fd, buff, strlen(buff) + 1, 0);        memset(buff, 0, sizeof(buff));        int size = recv(fd, buff, sizeof(buff), 0);        if (size > 0) {            printf("server say: %s\n", buff);        } else if(size == 0) {            printf("服务器断开毗连\n");            break;        } else {            perror("read fail...\n");            break;        }        sleep(1);    }    close(fd);
  2.     return 0;}
复制代码
运行效果


多线程服务端的实现

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

吴旭华

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表