• 【网络】网络基础&&套接字编程详解


     

    目录

    网络初识

    1、网络协议初识

    2、网络协议的分层:

    OSI分层模型--->网络理论模型:

    TCP/IOP五层模型--->工业中采用的网络模型

    为什么要有网络分层?

    3、网络数据的封装与复用

    4、IP地址和MAC的地址 

    IP地址

     MAC地址

    套接字编程 

    1、预备知识

    1、认识端口--->port

    2、网络数据的五元组信息 

    3、网络字节序

    4、主机字节序与网络字节序的互相转化 

    5、TCP协议与UDP协议的特性和区别 

     2、UDP_socket编程

    1、流程

    2、接口:

    3、代码实现(注意其中的一些细节问题) 

    3、TCP_socket编程

    1、流程

    2、接口:

    3、代码 


    网络初识

    1、网络协议初识

    协议:约定沟通双方传递信息的格式

    网络完成的事:将数据从主机A的a进程传递到主机B的b进程。

    网络协议:约定网络主机在传输数据时候的格式。 

    ①网络数据 = 应用层数据 + 协议部分

    • 在网络中的数据不仅仅只有数据本身,还需要有协议的内容,协议的内容帮助要传输的数据正确的在网络当中传输到对方的主机上。

    ②基于操作系统和库函数写出来的程序,都是应用层程序,产生的数据称之为应用层数据。

    ③协议部分,在Linux操作系统中也是采用描述的手法,描述的本质就是一个结构体。换句话说,协议部分的内容就是结构体数据。

    2、网络协议的分层:

    OSI分层模型--->网络理论模型:

    TCP/IOP五层模型--->工业中采用的网络模型

    从顶层到底层分别为:应用层、传输层、网络层、数据链路层、物理层

    为什么要有网络分层?

    1、从软件方面考虑:

    将网络的功能解耦开来,有负责应用层数据、有负责端与端之间的传输,有负责路由等等。

    2、从实现层面讲:

    分层当中各个协议完成各自的协议功能即可,只需要将不同层之间互相通信的接口设计匹配就行。至于每一层的内部实现什么功能,如何实现,这些都可以单独考虑,也就便于实现。

    3、网络数据的封装与复用

    结论:

    1、应用层数据经过网络传输的时候,需要经过网络协议栈的封装,到达对端之后,需要经过网络协议栈的分用。

    2、网络协议栈封装的时候,是增加了协议的内容。目的是为了在网络中能够正确传输。 

    4、IP地址和MAC的地址 

    IP地址

    分类:IPv4和IPv6两个版本的IP地址

    本质:IPv4版本的IP地址,本质上是一个无符号的32位整数,范围是[0, 2^32 - 1]

    作用:在网络中标识一台主机

    表现形式:通常使用“点分十进制”的字符串表示IP地址,例如192.168.0.1;用点分割的每一个数字表示一个字节,范围是0 - 255。

    注意:

    • 一个IP地址只能被一台机器占用
    • 一台机器可以占有多个IP地址

     MAC地址

    本质:长度为48比特位即为6字节,一般使用16进制数据加上冒号的方式来表示。(例如:08:89:24:fc:19:02)

    作用:

    • 标识具体的某一块物理网卡设备,网卡设备在出厂的时候,都会打上全球独一无二的MAC地址
    • MAC地址用来标识数据链路层中相连的节点

    使用ifconfig命令查看自己的Linux机器的网卡信息:

    套接字编程 

    1、预备知识

    1、认识端口--->port

    本质:端口号是一个2字节16位的无符号整数,范围是[0,65535]

    作用:端口号是用来标记一个进程,告诉操作系统,当前的数据要交给哪一个进程来处理

    注意:

    • 一个端口只能被一个进程占用
    • 一个进程可以占用多个端口
    • [0,1023]范围内的端口已经被一些知名的协议所使用,我们在编写代码的时候不要使用该范围内的数据作为端口号。

    2、网络数据的五元组信息 

    名称作用
    源IP标识网络数据是从那一台主机发出的

    目的IP

    标识数据要去往哪一台主句
    源端口标识网络数据是从“源端口”对应的这台主机的哪个进程产生的
    目的端口通过目的IP找到目的主机之后,需要利用目的端口找到对应的进程
    协议标识双方传输数据时使用的协议

    3、网络字节序

     字节序又称端序或者位序。指的是多字节数据在内存中存放的顺序。

    我们接触的字节序分为两类:小端和大端

    主机字节序:指的是机器本身的字节序。

    网络字节序:规定网络传输数据的时候采用大端字节序进行传输

    4、主机字节序与网络字节序的互相转化 

    既然网络字节序是大端字节序,现在假设有AB两台主机,他们之间需要通过网络进行通信,我们分析A向B发送消息这一过程。

    A向B发送数据,通过网络传输时一定要转换为网络字节序,否则传输的数据可能会出错(这取决于主机A是大端还是小端机器)

    B从网络中接收A发送的数据时,也需要将数据从网络字节序转换为B主机的主机字节序

    操作系统提供了转化的接口:

    主机字节序--->网络字节序:

            ip:uint32_t

                    uint32_t htol(uint32_t hostlong)

            port:unit16_t

                    unit16_t htos(uint16_t hostshort)

    网络字节序--->主机字节序:

            ip:unit32_t

                    unit32_t ntohl(unit32_t netlong)

            port:uint16_t

                    uint16_t ntohs(unit16_t netshort)

    5、TCP协议与UDP协议的特性和区别 

    UDP:无连接,不可靠,面向数据报

    无连接:UDP双方在发送数据之前,是不需要进行沟通的。只需要知道对方的IP和端口(不关心对方进程是否准备好通信),就可以通信。

    不可靠:不保证UDP数据是可靠、有序的到达对方。

    面向数据报:UDP在和应用层或者网络层递交数据的时候,都是整条数据进行交付的

    TCP:面向连接、可靠传输、面向字节流

    面向连接:TCP双方在发送数据之前先会建立连接

    可靠传输:TCP保证传输的数据是可靠、有序的到达对端的

    面向字节流:对于传输的数据没有明显的边界;对于接收方而言,可以按照任意的字节进行接收;好处:提升了传输效率。存在的问题:TCP粘包问题

     2、UDP_socket编程

    1、流程

    服务端:

    1. 创建套接字
    2. 绑定地址信息
    3. 收发消息
    4. 使用完毕后关闭套接字

    客户端:

    1. 创建套接字
    2. 不推荐绑定地址信息(不推荐在代码手动绑定地址信息),一个端口之能被一个进程占用,防止出现多个进程绑定同一个端口。
    3. 收发消息
    4. 使用完毕后关闭套接字

    如图所示:

    1、为什么要创建套接字?

    将进程和网卡进行绑定,进程可以从网卡中接收消息,也可以通过网卡发送消息。

    2、绑定地址信息具体干了什么?

    绑定IP和端口。目的是为了在网络中表示一台主机和一个进程。这样一来,对于接收方而言,发送数据的人就知道接收方在哪台机器哪个进程了;对于发送方而言,能够标识网络数据是从哪台机器的哪个进程发送出去的。 

    2、接口:

     1、创建套接字

    domain(地址域):

            选择一个具体的协议簇进行沟通。对于我们而言,UDP/TCP,可以认为在指定网络层 使用什么协议。具体:

            AF_UNIX:本地域套接字,在同一台机器使用 文件进行通信,不用跨机器

            AF_INET:IPV4版本的IP协议

            AF_INET6:IPV6版本的IP协议

    type(套接字的类型):

            SOCK_DGRAM:用户数据报套接字-----对应UDP

            SOCK_STREAM:流式套接字------对应TCP

    protocol(协议):

            0:标识按照套接字类型选择默认协议,SOCK_DGRAM-->UDP,SOCK_STREAM->TCP

            IPPROTO_TCP:对应数字6,代表TCP协议

            IPPROTO_UDP:对应数字17,代表UDP协议   

    返回值:返回套接字操作句柄,本质上就是一个文件描述符;大于等于0:创建成功;小于0:创建失败

     2、绑定接口

    sockfd:创建套接字时返回的套接字描述符

    addr:绑定的地址信息(IP + port)

    addrlen:绑定的地址信息的长度 

    注意:这里的struct sockaddr是一个通用的数据结构,结构如下: 

     我们在组织参数的时候,传递的并不是上面这个通用的数据结构,而是struct sockaddr_in这个结构体变量,具体内容如下

    1. struct sockaddr_in{
    2. //地址域
    3. sa_family_t sin_family;
    4. //端口号
    5. uint16_t sin_port;
    6. //32为IP地址
    7. struct in_addr sin_addr;
    8. //预留未使用
    9. char sin_zero[8];
    10. }
    1. struct in_addr{
    2. in_addr_t s_addr;
    3. }

    3、发送接口

    sockfd:套接字描述符

    buf:要发送的数据

    len:要发送数据的长度

    flags:0(阻塞发送)

    dest_addr:地址信息结构,包含了目的IP,目的端口;表示要把数据发送到哪里去。

    addrlen:地址信息的长度

    返回值:

    成功:返回正常发送的数据

    失败:返回-1 

    4、接收接口:

    sockfd:套接字描述符

    buf:程序员准备的接收数据的缓冲区

    len:最大能接收数据的大小,一般为缓冲区的大小

    flags:0(阻塞接受)

    src_addr:源IP + 源端口

    addrlen:是一个出参,返回地址信息的长度 

    5、关闭接口

    int close(int fd) 

    3、代码实现(注意其中的一些细节问题) 

    服务端:

    1. #include<stdio.h>
    2. 2 #include<sys/socket.h>
    3. 3 #include <netinet/in.h>
    4. 4 #include <arpa/inet.h>
    5. 5 #include<string.h>
    6. 6 #include<unistd.h>
    7. 7 int main(){
    8. 8 /*
    9. 9 * 1.创建套接字
    10. 10 * 2.绑定地址信息
    11. 11 * 3.接受信息
    12. 12 * 4.发送消息
    13. 13 * */
    14. 14
    15. 15 int udp_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    16. 16 if(udp_fd < 0){
    17. 17 perror("socket");
    18. 18 return 0;
    19. 19 }
    20. 20 //绑定地址信息
    21. 21 struct sockaddr_in addr;
    22. 22 addr.sin_family = AF_INET;
    23. 23 addr.sin_port = htons(22222); //这里的端口号必须转化为网络字节序
    24. 24 /*
    25. 25 * in_addr_t inet_addr(const char *cp);
    26. 26 * 1.将点分十进制的ip转化成为无符号的32位整数
    27. 27 * 2.将无符号32位整数转换成为网络字节序
    28. 28 * */
    29. 29 addr.sin_addr.s_addr = inet_addr("0.0.0.0");
    30. 30 int ret = bind(udp_fd, (struct sockaddr*)&addr, sizeof(addr));
    31. 31 if(ret < 0){
    32. 32 perror("bind");
    33. 33 return 0;
    34. 34 }
    35. 35 //接受消息
    36. 36 char buf[1024];
    37. 37 struct sockaddr_in rcv_addr;
    38. 38 socklen_t rcv_addr_len = sizeof(rcv_addr);
    39. 39 ssize_t rcv_size = recvfrom(udp_fd, buf, sizeof(buf) - 1, 0, (struct sockaddr*)&rcv_addr, &rcv_addr_len);
    40. 40 if(rcv_size < 0){
    41. 41 return 0;
    42. 42 }
    43. 43 /*
    44. 44 * inet_ntoa:
    45. 45 * 1、将unit32位整型数字转换为点分十进制的字符串
    46. 46 * 2、将网络字节序转化为主机字节序
    47. 47 * */
    48. 48
    49. 49 printf("%s : %d client send value is: %s\n", inet_ntoa(rcv_addr.sin_addr), ntohs(rcv_addr.sin_port), buf);
    50. 50 memset(buf, '\0', sizeof(buf));
    51. 51 sprintf(buf, "hello , I am server");
    52. 52 //发送消息
    53. 53 ssize_t send_size = sendto(udp_fd, buf, strlen(buf), 0, (struct sockaddr*)&rcv_addr, sizeof(rcv_addr));
    54. 54 if(send_size < 0){
    55. 55 return 0;
    56. 56 }
    57. 57 close(udp_fd);
    58. 58 return 0;
    59. 59 }

     客户端:

    1. #include<stdio.h>
    2. 2 #include<sys/socket.h>
    3. 3 #include<arpa/inet.h>
    4. 4 #include<netinet/in.h>
    5. 5 #include<string.h>
    6. 6 #include<unistd.h>
    7. 7 int main()
    8. 8 {
    9. 9 /*
    10. 10 *1、创建套接字
    11. 11 *2、发送消息
    12. 12 *3、接收消息
    13. 13 * */
    14. 14 int udp_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    15. 15 if(udp_fd < 0)
    16. 16 {
    17. 17 perror("socket");
    18. 18 return 0;
    19. 19 }
    20. 20 //发送消息
    21. 21 struct sockaddr_in addr;
    22. 22 addr.sin_family = AF_INET;
    23. 23 addr.sin_port = htons(22222);
    24. 24 addr.sin_addr.s_addr = inet_addr("10.0.24.13");
    25. 25
    26. 26 const char* str = "Hello, I am udp_client!";
    27. 27 ssize_t send_size = sendto(udp_fd, str, strlen(str), 0 ,(struct sockaddr*)&addr, sizeof(addr));
    28. 28 if(send_size < 0)
    29. 29 {
    30. 30 perror("sendto");
    31. 31 return 0;
    32. 32 }
    33. 33 //接收消息
    34. 34 char buf[1024] = {0};
    35. 35 struct sockaddr_in serve_addr;
    36. 36 socklen_t serve_addrlen = sizeof(serve_addr);
    37. 37 ssize_t rcv_size = recvfrom(udp_fd, buf, sizeof(buf) - 1, 0, (struct sockaddr*)&serve_addr, &serve_addrlen);
    38. 38 if(rcv_size < 0){
    39. 39 return 0;
    40. 40 }
    41. 41 printf("%s : %d send value is : %s\n", inet_ntoa(serve_addr.sin_addr), ntohs(serve_addr.sin_port), buf);
    42. 42 close(udp_fd);
    43. 43 return 0;
    44. 44 }

    查看端口信息: 

     结果:

    3、TCP_socket编程

    1、流程

    服务端:

    创建套接字

    绑定地址信息

    监听

    获取新连接

    收发消息

    关闭连接

    客户端:

    创建套接字

    不推荐绑定地址信息

    发起连接

    收发数据

    关闭连接 

    如图:

     监听的含义:

    监听TCP客户端新的连接,同客户端建立TCP连接。(此时,TCP的建立在内核中就完成了)

     获取新连接的含义:

    获取新连接的套接字描述符,每一个TCP连接会产生一个新的套接字描述符。

     发起连接:向服务端发起连接

    2、接口:

    1、监听

    sockfd->套接字描述符:一般为服务端创建套接字时产生的套接字描述符

    backlog(TCP并发连接数,也就是一瞬间可以建立多少连接):

    • Linux2.2版本之前:未完成连接请求的数量
    • Linux2.2之后:TCP并发连接数即已完成连接的大小。

    未完成连接的队列:还处于建立连接的连接被放到这个队列中,可以理解为正在3次握手的连接在该队列中。

    已完成连接的队列:连接已经建立,可以正常通信的连接放在这个队列中,可以理解为三次握手完毕的连接在该队列中。

    可以通过修改/proc/sys/net/ipv4/tcp_max_syn_backlog当中的值,修改未完成连接队列的大小。

    返回值:

    成功返回0,失败返回-1。

    2、获取新连接

    sockfd:套接字描述符

    addr:地址信息结构体,用来描述客户端地址信息

    addrlen:地址信息长度

    返回值:成功返回新连接的套接字,失败返回-1 

    注意:

    该接口具有阻塞属性

    1. 如果已完成连接队列中没有已经建立的连接,则阻塞
    2. 如果有,获取新连接后返回。

     注意:

    1. 返回的新连接的套接字,是为了和客户端进行通信的,只不过这个套接字没有监听功能,同时有客户端的地址信息。
    2. 多个客户端发起连接,在服务端会创建多个新连接的套接字。
    3. 服务端使用socket创建的套接字描述符,是一个侦听套接字,主要责任就是侦听是否有新的连接到来;服务端使用accept创建出来的套接字,被称为新连接套接字,主要责任就是同客户端通信。

    3、发起连接

     sockfd:套接字描述符,一般为客户端创建套接字时的返回值

    addr:地址信息结构,描述服务端的地址信息(服务端的IP和port)

    addrlen:四肢信息长度

    返回值:成功返回0,失败返回-1

    该函数不仅可以完成连接功能。如果客户没有进行绑定,同时也会可定客户端的地址信息。 

    4、接收数据

    sockfd:套接字描述符,谁接收就传谁的描述符

    buf:将接收的数据存放在buf指定的空间

    len:期望接收的字节个数

    flags:0(阻塞接收)

    返回值:成功收到字节的数量;失败:0,对端关闭连接了;-1,接收错误。

    注意:

    返回值为0表示对端关闭连接了,如果此时的对端指的是客户端,则服务端需要将对端的新套接字描述符关闭。

    5、发送数据

    sockfd:套接字描述符(服务端的话,传递的是新创建的套接字描述符)

    buf:发送buf指向的空间的内容

    len:数据长度

    flags:0(阻塞发送)

    返回值:成功,返回发送的字节数量;失败,返回-1。 

    3、代码 

    要求:服务端可以和多个客户端之间能够正常通信。

    服务端:

    1. #include<stdio.h>
    2. 2 #include<unistd.h>
    3. 3 #include<sys/socket.h>
    4. 4 #include<arpa/inet.h>
    5. 5 #include<netinet/in.h>
    6. 6 #include<string.h>
    7. 7 int main(){
    8. 8 /*
    9. 9 * 1、创建服务端套接字(侦听套接字)
    10. 10 * 2、绑定地址信息
    11. 11 * 3、监听
    12. 12 * 4、获取新连接
    13. 13 * 5、收发消息
    14. 14 * */
    15. 15 int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
    16. 16 if(listen_fd < 0){
    17. 17 perror("socket");
    18. 18 return 0;
    19. 19 }
    20. 20 struct sockaddr_in addr;
    21. 21 addr.sin_family = AF_INET;
    22. 22 addr.sin_port = htons(22222);
    23. 23 addr.sin_addr.s_addr = inet_addr("0.0.0.0");
    24. 24
    25. 25 int ret = bind(listen_fd, (struct sockaddr*)&addr, sizeof(addr));
    26. 26 if(ret < 0){
    27. 27 perror("bind");
    28. 28 return 0;
    29. 29 }
    30. 30 //监听
    31. 31 ret = listen(listen_fd, 5);
    32. 32 if(ret < 0){
    33. 33 perror("listen");
    34. 34 return 0;
    35. 35 }
    36. 36 //接收新连接
    37. 37 struct sockaddr_in cli_addr;
    38. 38 socklen_t cli_addr_len = sizeof(cli_addr);
    39. 39 int new_sockfd = accept(listen_fd, (struct sockaddr*)&cli_addr, &cli_addr_len);
    40. 40 if(new_sockfd < 0){
    41. 41 perror("accept");
    42. 42 return 0;
    43. 43 }
    44. 44 //收发消息
    45. 45 while(1){
    46. 46 char buf[1024] = {0};
    47. 47 int recv_size = recv(new_sockfd, buf, sizeof(buf) - 1, 0);
    48. 48 if(recv_size < 0){
    49. 49 perror("recv");
    50. 50 return 0;
    51. 51 }else if(recv_size == 0){
    52. 52 //客户端将连接关闭了
    53. 53 printf("perr shutdown\n");
    54. 54 close(new_sockfd);
    55. 55 return 0;
    56. 56 }
    57. 57 printf("buf is [%s]\n", buf);
    58. 58 memset(buf, '\0', sizeof(buf));
    59. 59 //发送消息
    60. 60 sprintf(buf, "[%s]:[%d] I am serve, i recv your msg", inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port));
    61. 61 send(new_sockfd, buf, strlen(buf), 0);
    62. 62 }
    63. 63 close(listen_fd);
    64. 64 return 0;
    65. 65 }
    66. ~

    客户端:

    1. include<stdio.h>
    2. 2 #include<unistd.h>
    3. 3 #include<sys/socket.h>
    4. 4 #include<string.h>
    5. 5 #include<arpa/inet.h>
    6. 6 #include<netinet/in.h>
    7. 7 #include<iostream>
    8. 8
    9. 9 using namespace std;
    10. 10 int main(){
    11. 11 /*
    12. 12 * 1、创建套接字
    13. 13 * 2、建立连接
    14. 14 * 3、收发消息
    15. 15 * */
    16. 16 int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    17. 17 if(sockfd < 0){
    18. 18 perror("socket");
    19. 19 return 0;
    20. 20 }
    21. 21 //connect
    22. 22 struct sockaddr_in addr;
    23. 23 addr.sin_family = AF_INET;
    24. 24 addr.sin_port = htons(22222);
    25. 25 addr.sin_addr.s_addr = inet_addr("10.0.24.13");
    26. 26
    27. 27 int ret = connect(sockfd, (struct sockaddr*)&addr, sizeof(addr));
    28. 28 if(ret < 0){
    29. 29 perror("connect");
    30. 30 return 0;
    31. 31 }
    32. 32 while(1){
    33. 33 char buf[1024] = {0};
    34. 34 cout << "please enter your msg#" << endl;
    35. 35 fflush(stdout);
    36. 36
    37. 37 cin >> buf;
    38. 38 int send_size = send(sockfd, buf, strlen(buf), 0);
    39. 39 if(send_size < 0){
    40. 40 perror("send");
    41. 41 continue;
    42. 42 }
    43. 43 //接收消息
    44. 44 memset(buf, '\0', sizeof(buf));
    45. 45 int recv_size = recv(sockfd, buf, sizeof(buf) - 1, 0);
    46. 46 if(recv_size < 0){
    47. 47 perror("send");
    48. 48 return 0;
    49. 49 }else if(recv_size == 0){
    50. 50 //服务端将连接关闭
    51. 51 cout << "server shutdown connect" << endl;
    52. 52 }
    53. 53 printf("%s\n", buf);
    54. 54 }
    55. 55 close(sockfd);
    56. 56 return 0;
    57. 57 }
    58. ~

    结果:

     查看当前网络连接状态:

    分析原因:

    accept和recv接口都具有阻塞属性,对于当前代码来说,服务端在接收到第一个客户端的消息后,循环上来继续阻塞在recv接口处等待接收消息,并不会执行accpet函数,也就不会从已完成连接队列中读取套接字描述符。 

    如果将accept接口放到循环内部,能否解决问题?

    不可以。由于accept和recv都具有阻塞属性,会导致accept阻塞属性影响recv的接收,recv属性会影响accept获取新连接,并且accept获取到的新连接套接字B会覆盖上一次获取到的新连接套接字A。

    综上所述,单线程的TCP代码就目前而言只能服务于单个客户端的情况。(后续可以通过多路转接IO模型实现与客户端一对多的情况)

    我们可以让服务端的一个进程(线程)只负责与客户端建立连接,剩下的一批进程(线程)可以各自与一个客户端进行沟通。这样就可以达到目标。

    ②TCP + 多进程

    首先,对于客户端的代码而言,不需要作出任何的改动!因为客户端只需要一直和服务端进行通信即可。

    主要更改是在服务端,我们通过创建子进程的方式来实现职责的分离,也就是父进程值负责与客户端建立连接,而子进程负责与客户端进行收发消息。

    注意:

    1. 子进程是拷贝父进程的PCB,因此需要父进程先与客户端建立连接,也即在父进程的PCB中的fd_array中有了该套接字的文件描述符之后再创建子进程。
    2. 子进程创建成功过,由于它只需要和客户端进行收发消息,因此只需要accpet返回的新套接字描述符即可,所以需要将拷贝自父进程的侦听套接字关闭。
    3. 客户端如果将连接关闭,则子进程需要将对应的套接字即文件描述符关闭,然后该进程需要退出。(退出时,一定要通知父进程来回收子进程的退出状态信息,否则子进程就会变成僵尸进程!但是我们不能采用wait | waitpid来回收。因为wait具有阻塞属性,而waitpid需要搭配循环来使用,均不符合预期。我们可以通过信号量的方式来处理,即改写SIGCHILD信号!
    1. #include<stdio.h>
    2. 2 #include<string.h>
    3. 3 #include<sys/socket.h>
    4. 4 #include<arpa/inet.h>
    5. 5 #include<netinet/in.h>
    6. 6 #include<signal.h>
    7. 7 #include<unistd.h>
    8. 8 #include<sys/wait.h>
    9. 9
    10. W> 10 void signalDeal(int signum){
    11. 11 wait(NULL);
    12. 12 }
    13. 13 int main(){
    14. 14 signal(SIGCHLD, signalDeal);
    15. 15 int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
    16. 16 if(listen_fd < 0){
    17. 17 perror("socket");
    18. 18 return 0;
    19. 19 }
    20. 20 struct sockaddr_in addr;
    21. 21 addr.sin_family = AF_INET;
    22. 22 addr.sin_port = htons(22222);
    23. 23 addr.sin_addr.s_addr = inet_addr("0.0.0.0");
    24. 24 int ret = bind(listen_fd, (struct sockaddr*)&addr, sizeof(addr));
    25. 25 if(ret < 0){
    26. 26 perror("bind");
    27. 27 return 0;
    28. 28 }
    29. 29 ret = listen(listen_fd, 5);
    30. 30 if(ret < 0){
    31. 31 perror("listen");
    32. 32 return 0;
    33. 33 }
    34. 34
    35. 35 //收发消息
    36. 36 while(1){
    37. 37 struct sockaddr_in cli_addr;
    38. 38 socklen_t cli_addr_len = sizeof(cli_addr);
    39. 39 int new_sockfd = accept(listen_fd, (struct sockaddr*)&cli_addr,&cli_addr_len);
    40. 40 if(new_sockfd < 0){
    41. 41 perror("accept");
    42. 42 return 0;
    43. 43 }
    44. 44 pid_t pid = fork();
    45. 45 if(pid < 0){
    46. 46 close(new_sockfd);
    47. 47 continue;
    48. 48 }else if(pid == 0){
    49. 49 //子进程负责与客户端沟通
    50. 50 close(listen_fd);
    51. 51 while(1){
    52. 52 char buf[1024] = {0};
    53. 53 int recv_size = recv(new_sockfd, buf, sizeof(buf) - 1, 0);
    54. 54 if(recv_size < 0){
    55. 55 perror("recv");
    56. 56 continue;
    57. 57 }else if(recv_size == 0){
    58. 58 //客户端将连接关闭
    59. 59 printf("perr shutdown!\n");
    60. 60 close(new_sockfd);
    61. 61 return 0;
    62. 62 }
    63. 63 printf("[%s]:[%d] say [%s]\n", inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port), buf);
    64. 64 memset(buf, '\0', sizeof(buf));
    65. 65 sprintf(buf, "I am server, I recv your msg");
    66. 66 send(new_sockfd, buf, strlen(buf), 0);
    67. 67 }
    68. 68 }else{
    69. 69 close(new_sockfd);
    70. 70 }
    71. 71 }
    72. 72 return 0;
    73. 73 }
    74. ~

     

     ③tcp + 多线程

    1. 1 #include<stdio.h>
    2. 2 #include<pthread.h>
    3. 3 #include<sys/socket.h>
    4. 4 #include<arpa/inet.h>
    5. 5 #include<netinet/in.h>
    6. 6 #include<string.h>
    7. 7 #include<unistd.h>
    8. 8
    9. 9 //将新连接描述符和客户端地址信息封装传给线程入口函数
    10. 10 struct cli_info{
    11. 11 cli_info()
    12. 12 :_new_sockfd(-1){
    13. 13 memset(&addr, '0', sizeof(addr));
    14. 14 }
    15. 15 int _new_sockfd;
    16. 16 struct sockaddr_in addr;
    17. 17 };
    18. 18
    19. 19 void* tcp_deal_start(void* arg){
    20. 20 pthread_detach(pthread_self());
    21. 21 cli_info* ci = (cli_info*)arg;
    22. 22 int new_sockfd = ci->_new_sockfd;
    23. 23 struct sockaddr_in cli_addr = ci->addr;
    24. 24 //收发消息
    25. 25 while(1){
    26. 26 char buf[1024] = {0};
    27. 27 int recv_size = recv(new_sockfd, buf, sizeof(buf) - 1, 0);
    28. 28 if(recv_size < 0){
    29. 29 perror("recb");
    30. 30 continue;
    31. 31 }else if(recv_size == 0){
    32. 32 //客户端关闭连接
    33. 33 printf("perr shutdown\n");
    34. 34 delete ci;
    35. 35 close(new_sockfd);
    36. 36 return 0;
    37. 37 }
    38. 38 printf("[%s]:[%d] say [%s]\n", inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port), buf);
    39. 39 memset(buf, '\0', sizeof(buf));
    40. 40 sprintf(buf, "[%s] : [%d] I am serve, I recv your msg", inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port));
    41. 41 send(new_sockfd, buf, strlen(buf), 0);
    42. 42 }
    43. 43 }
    44. 44 int main(){
    45. 45 int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
    46. 46 if(listen_fd < 0){
    47. 47 perror("listen");
    48. 48 return 0;
    49. 49 }
    50. 50 struct sockaddr_in addr;
    51. 51 addr.sin_family = AF_INET;
    52. 52 addr.sin_port = htons(22222);
    53. 53 addr.sin_addr.s_addr = inet_addr("0.0.0.0");
    54. 54 int ret = bind(listen_fd, (struct sockaddr*)&addr, sizeof(addr));
    55. 55 if(ret < 0){
    56. 56 perror("bind");
    57. 57 return 0;
    58. 58 }
    59. 59 ret = listen(listen_fd, 5);
    60. 60 if(ret < 0){
    61. 61 perror("listen");
    62. 62 return 0;
    63. 63 }
    64. 64
    65. 65 //收发消息
    66. 66 while(1){
    67. 67 struct sockaddr_in cli_addr;
    68. 68 socklen_t cli_addr_len = sizeof(cli_addr);
    69. 69 int new_sockfd = accept(listen_fd, (struct sockaddr*)&cli_addr, &cli_addr_len);
    70. 70 if(new_sockfd < 0){
    71. 71 continue;
    72. 72 }
    73. 73 pthread_t tid;
    74. 74 cli_info* ci = new cli_info();
    75. 75 ci->_new_sockfd = new_sockfd;
    76. 76 memcpy(&ci->addr, &cli_addr, sizeof(cli_addr));
    77. 77 int ret = pthread_create(&tid, NULL, tcp_deal_start, (void*)ci);
    78. 78 if(ret < 0){
    79. 79 close(new_sockfd);
    80. 80 continue;
    81. 81 }
    82. 82 }
    83. 83 return 0;
    84. 84 }

  • 相关阅读:
    pip 如何设置代理
    debian中vim的使用
    QSpinBox 旋转框/微调按钮
    java基于ssm的汽车维修工时费快速估价系统
    SpringBoot加载静态资源
    北理工操作系统实验合集 | API解读与例子(持续更新)
    前端uniapp如何转base64使用uniapp插件市场
    flink 流计算一条一条处理日志
    同样的数据图片和模型,在kaggle上训练和在本地训练差距怎么那么大
    【数据结构】插入排序
  • 原文地址:https://blog.csdn.net/weixin_62042704/article/details/125324606