目录
协议:约定沟通双方传递信息的格式
网络完成的事:将数据从主机A的a进程传递到主机B的b进程。
网络协议:约定网络主机在传输数据时候的格式。
①网络数据 = 应用层数据 + 协议部分
②基于操作系统和库函数写出来的程序,都是应用层程序,产生的数据称之为应用层数据。
③协议部分,在Linux操作系统中也是采用描述的手法,描述的本质就是一个结构体。换句话说,协议部分的内容就是结构体数据。
从顶层到底层分别为:应用层、传输层、网络层、数据链路层、物理层
1、从软件方面考虑:
将网络的功能解耦开来,有负责应用层数据、有负责端与端之间的传输,有负责路由等等。
2、从实现层面讲:
分层当中各个协议完成各自的协议功能即可,只需要将不同层之间互相通信的接口设计匹配就行。至于每一层的内部实现什么功能,如何实现,这些都可以单独考虑,也就便于实现。
结论:
1、应用层数据经过网络传输的时候,需要经过网络协议栈的封装,到达对端之后,需要经过网络协议栈的分用。
2、网络协议栈封装的时候,是增加了协议的内容。目的是为了在网络中能够正确传输。
分类:IPv4和IPv6两个版本的IP地址
本质:IPv4版本的IP地址,本质上是一个无符号的32位整数,范围是[0, 2^32 - 1]
作用:在网络中标识一台主机
表现形式:通常使用“点分十进制”的字符串表示IP地址,例如192.168.0.1;用点分割的每一个数字表示一个字节,范围是0 - 255。
注意:
- 一个IP地址只能被一台机器占用
- 一台机器可以占有多个IP地址
本质:长度为48比特位即为6字节,一般使用16进制数据加上冒号的方式来表示。(例如:08:89:24:fc:19:02)
作用:
- 标识具体的某一块物理网卡设备,网卡设备在出厂的时候,都会打上全球独一无二的MAC地址
- MAC地址用来标识数据链路层中相连的节点
使用ifconfig命令查看自己的Linux机器的网卡信息:
本质:端口号是一个2字节16位的无符号整数,范围是[0,65535]
作用:端口号是用来标记一个进程,告诉操作系统,当前的数据要交给哪一个进程来处理
注意:
- 一个端口只能被一个进程占用
- 一个进程可以占用多个端口
- [0,1023]范围内的端口已经被一些知名的协议所使用,我们在编写代码的时候不要使用该范围内的数据作为端口号。
名称 | 作用 |
源IP | 标识网络数据是从那一台主机发出的 |
目的IP | 标识数据要去往哪一台主句 |
源端口 | 标识网络数据是从“源端口”对应的这台主机的哪个进程产生的 |
目的端口 | 通过目的IP找到目的主机之后,需要利用目的端口找到对应的进程 |
协议 | 标识双方传输数据时使用的协议 |
字节序又称端序或者位序。指的是多字节数据在内存中存放的顺序。
我们接触的字节序分为两类:小端和大端
主机字节序:指的是机器本身的字节序。
网络字节序:规定网络传输数据的时候采用大端字节序进行传输
既然网络字节序是大端字节序,现在假设有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)
UDP:无连接,不可靠,面向数据报
无连接:UDP双方在发送数据之前,是不需要进行沟通的。只需要知道对方的IP和端口(不关心对方进程是否准备好通信),就可以通信。
不可靠:不保证UDP数据是可靠、有序的到达对方。
面向数据报:UDP在和应用层或者网络层递交数据的时候,都是整条数据进行交付的
TCP:面向连接、可靠传输、面向字节流
面向连接:TCP双方在发送数据之前先会建立连接
可靠传输:TCP保证传输的数据是可靠、有序的到达对端的
面向字节流:对于传输的数据没有明显的边界;对于接收方而言,可以按照任意的字节进行接收;好处:提升了传输效率。存在的问题:TCP粘包问题
服务端:
- 创建套接字
- 绑定地址信息
- 收发消息
- 使用完毕后关闭套接字
客户端:
- 创建套接字
- 不推荐绑定地址信息(不推荐在代码手动绑定地址信息),一个端口之能被一个进程占用,防止出现多个进程绑定同一个端口。
- 收发消息
- 使用完毕后关闭套接字
如图所示:
1、为什么要创建套接字?
将进程和网卡进行绑定,进程可以从网卡中接收消息,也可以通过网卡发送消息。
2、绑定地址信息具体干了什么?
绑定IP和端口。目的是为了在网络中表示一台主机和一个进程。这样一来,对于接收方而言,发送数据的人就知道接收方在哪台机器哪个进程了;对于发送方而言,能够标识网络数据是从哪台机器的哪个进程发送出去的。
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这个结构体变量,具体内容如下:
- struct sockaddr_in{
- //地址域
- sa_family_t sin_family;
- //端口号
- uint16_t sin_port;
- //32为IP地址
- struct in_addr sin_addr;
- //预留未使用
- char sin_zero[8];
- }
- struct in_addr{
- in_addr_t s_addr;
- }
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)
服务端:
- #include<stdio.h>
- 2 #include<sys/socket.h>
- 3 #include <netinet/in.h>
- 4 #include <arpa/inet.h>
- 5 #include<string.h>
- 6 #include<unistd.h>
- 7 int main(){
- 8 /*
- 9 * 1.创建套接字
- 10 * 2.绑定地址信息
- 11 * 3.接受信息
- 12 * 4.发送消息
- 13 * */
- 14
- 15 int udp_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
- 16 if(udp_fd < 0){
- 17 perror("socket");
- 18 return 0;
- 19 }
- 20 //绑定地址信息
- 21 struct sockaddr_in addr;
- 22 addr.sin_family = AF_INET;
- 23 addr.sin_port = htons(22222); //这里的端口号必须转化为网络字节序
- 24 /*
- 25 * in_addr_t inet_addr(const char *cp);
- 26 * 1.将点分十进制的ip转化成为无符号的32位整数
- 27 * 2.将无符号32位整数转换成为网络字节序
- 28 * */
- 29 addr.sin_addr.s_addr = inet_addr("0.0.0.0");
- 30 int ret = bind(udp_fd, (struct sockaddr*)&addr, sizeof(addr));
- 31 if(ret < 0){
- 32 perror("bind");
- 33 return 0;
- 34 }
- 35 //接受消息
- 36 char buf[1024];
- 37 struct sockaddr_in rcv_addr;
- 38 socklen_t rcv_addr_len = sizeof(rcv_addr);
- 39 ssize_t rcv_size = recvfrom(udp_fd, buf, sizeof(buf) - 1, 0, (struct sockaddr*)&rcv_addr, &rcv_addr_len);
- 40 if(rcv_size < 0){
- 41 return 0;
- 42 }
- 43 /*
- 44 * inet_ntoa:
- 45 * 1、将unit32位整型数字转换为点分十进制的字符串
- 46 * 2、将网络字节序转化为主机字节序
- 47 * */
- 48
- 49 printf("%s : %d client send value is: %s\n", inet_ntoa(rcv_addr.sin_addr), ntohs(rcv_addr.sin_port), buf);
- 50 memset(buf, '\0', sizeof(buf));
- 51 sprintf(buf, "hello , I am server");
- 52 //发送消息
- 53 ssize_t send_size = sendto(udp_fd, buf, strlen(buf), 0, (struct sockaddr*)&rcv_addr, sizeof(rcv_addr));
- 54 if(send_size < 0){
- 55 return 0;
- 56 }
- 57 close(udp_fd);
- 58 return 0;
- 59 }
客户端:
- #include<stdio.h>
- 2 #include<sys/socket.h>
- 3 #include<arpa/inet.h>
- 4 #include<netinet/in.h>
- 5 #include<string.h>
- 6 #include<unistd.h>
- 7 int main()
- 8 {
- 9 /*
- 10 *1、创建套接字
- 11 *2、发送消息
- 12 *3、接收消息
- 13 * */
- 14 int udp_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
- 15 if(udp_fd < 0)
- 16 {
- 17 perror("socket");
- 18 return 0;
- 19 }
- 20 //发送消息
- 21 struct sockaddr_in addr;
- 22 addr.sin_family = AF_INET;
- 23 addr.sin_port = htons(22222);
- 24 addr.sin_addr.s_addr = inet_addr("10.0.24.13");
- 25
- 26 const char* str = "Hello, I am udp_client!";
- 27 ssize_t send_size = sendto(udp_fd, str, strlen(str), 0 ,(struct sockaddr*)&addr, sizeof(addr));
- 28 if(send_size < 0)
- 29 {
- 30 perror("sendto");
- 31 return 0;
- 32 }
- 33 //接收消息
- 34 char buf[1024] = {0};
- 35 struct sockaddr_in serve_addr;
- 36 socklen_t serve_addrlen = sizeof(serve_addr);
- 37 ssize_t rcv_size = recvfrom(udp_fd, buf, sizeof(buf) - 1, 0, (struct sockaddr*)&serve_addr, &serve_addrlen);
- 38 if(rcv_size < 0){
- 39 return 0;
- 40 }
- 41 printf("%s : %d send value is : %s\n", inet_ntoa(serve_addr.sin_addr), ntohs(serve_addr.sin_port), buf);
- 42 close(udp_fd);
- 43 return 0;
- 44 }
查看端口信息:
结果:
服务端:
创建套接字
绑定地址信息
监听
获取新连接
收发消息
关闭连接
客户端:
创建套接字
不推荐绑定地址信息
发起连接
收发数据
关闭连接
如图:
监听的含义:
监听TCP客户端新的连接,同客户端建立TCP连接。(此时,TCP的建立在内核中就完成了)
获取新连接的含义:
获取新连接的套接字描述符,每一个TCP连接会产生一个新的套接字描述符。
发起连接:向服务端发起连接
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
注意:
该接口具有阻塞属性
- 如果已完成连接队列中没有已经建立的连接,则阻塞
- 如果有,获取新连接后返回。
注意:
- 返回的新连接的套接字,是为了和客户端进行通信的,只不过这个套接字没有监听功能,同时有客户端的地址信息。
- 多个客户端发起连接,在服务端会创建多个新连接的套接字。
- 服务端使用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。
要求:服务端可以和多个客户端之间能够正常通信。
①
服务端:
- #include<stdio.h>
- 2 #include<unistd.h>
- 3 #include<sys/socket.h>
- 4 #include<arpa/inet.h>
- 5 #include<netinet/in.h>
- 6 #include<string.h>
- 7 int main(){
- 8 /*
- 9 * 1、创建服务端套接字(侦听套接字)
- 10 * 2、绑定地址信息
- 11 * 3、监听
- 12 * 4、获取新连接
- 13 * 5、收发消息
- 14 * */
- 15 int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
- 16 if(listen_fd < 0){
- 17 perror("socket");
- 18 return 0;
- 19 }
- 20 struct sockaddr_in addr;
- 21 addr.sin_family = AF_INET;
- 22 addr.sin_port = htons(22222);
- 23 addr.sin_addr.s_addr = inet_addr("0.0.0.0");
- 24
- 25 int ret = bind(listen_fd, (struct sockaddr*)&addr, sizeof(addr));
- 26 if(ret < 0){
- 27 perror("bind");
- 28 return 0;
- 29 }
- 30 //监听
- 31 ret = listen(listen_fd, 5);
- 32 if(ret < 0){
- 33 perror("listen");
- 34 return 0;
- 35 }
- 36 //接收新连接
- 37 struct sockaddr_in cli_addr;
- 38 socklen_t cli_addr_len = sizeof(cli_addr);
- 39 int new_sockfd = accept(listen_fd, (struct sockaddr*)&cli_addr, &cli_addr_len);
- 40 if(new_sockfd < 0){
- 41 perror("accept");
- 42 return 0;
- 43 }
- 44 //收发消息
- 45 while(1){
- 46 char buf[1024] = {0};
- 47 int recv_size = recv(new_sockfd, buf, sizeof(buf) - 1, 0);
- 48 if(recv_size < 0){
- 49 perror("recv");
- 50 return 0;
- 51 }else if(recv_size == 0){
- 52 //客户端将连接关闭了
- 53 printf("perr shutdown\n");
- 54 close(new_sockfd);
- 55 return 0;
- 56 }
- 57 printf("buf is [%s]\n", buf);
- 58 memset(buf, '\0', sizeof(buf));
- 59 //发送消息
- 60 sprintf(buf, "[%s]:[%d] I am serve, i recv your msg", inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port));
- 61 send(new_sockfd, buf, strlen(buf), 0);
- 62 }
- 63 close(listen_fd);
- 64 return 0;
- 65 }
- ~
客户端:
- include<stdio.h>
- 2 #include<unistd.h>
- 3 #include<sys/socket.h>
- 4 #include<string.h>
- 5 #include<arpa/inet.h>
- 6 #include<netinet/in.h>
- 7 #include<iostream>
- 8
- 9 using namespace std;
- 10 int main(){
- 11 /*
- 12 * 1、创建套接字
- 13 * 2、建立连接
- 14 * 3、收发消息
- 15 * */
- 16 int sockfd = socket(AF_INET, SOCK_STREAM, 0);
- 17 if(sockfd < 0){
- 18 perror("socket");
- 19 return 0;
- 20 }
- 21 //connect
- 22 struct sockaddr_in addr;
- 23 addr.sin_family = AF_INET;
- 24 addr.sin_port = htons(22222);
- 25 addr.sin_addr.s_addr = inet_addr("10.0.24.13");
- 26
- 27 int ret = connect(sockfd, (struct sockaddr*)&addr, sizeof(addr));
- 28 if(ret < 0){
- 29 perror("connect");
- 30 return 0;
- 31 }
- 32 while(1){
- 33 char buf[1024] = {0};
- 34 cout << "please enter your msg#" << endl;
- 35 fflush(stdout);
- 36
- 37 cin >> buf;
- 38 int send_size = send(sockfd, buf, strlen(buf), 0);
- 39 if(send_size < 0){
- 40 perror("send");
- 41 continue;
- 42 }
- 43 //接收消息
- 44 memset(buf, '\0', sizeof(buf));
- 45 int recv_size = recv(sockfd, buf, sizeof(buf) - 1, 0);
- 46 if(recv_size < 0){
- 47 perror("send");
- 48 return 0;
- 49 }else if(recv_size == 0){
- 50 //服务端将连接关闭
- 51 cout << "server shutdown connect" << endl;
- 52 }
- 53 printf("%s\n", buf);
- 54 }
- 55 close(sockfd);
- 56 return 0;
- 57 }
- ~
结果:
查看当前网络连接状态:
分析原因:
accept和recv接口都具有阻塞属性,对于当前代码来说,服务端在接收到第一个客户端的消息后,循环上来继续阻塞在recv接口处等待接收消息,并不会执行accpet函数,也就不会从已完成连接队列中读取套接字描述符。
如果将accept接口放到循环内部,能否解决问题?
不可以。由于accept和recv都具有阻塞属性,会导致accept阻塞属性影响recv的接收,recv属性会影响accept获取新连接,并且accept获取到的新连接套接字B会覆盖上一次获取到的新连接套接字A。
综上所述,单线程的TCP代码就目前而言只能服务于单个客户端的情况。(后续可以通过多路转接IO模型实现与客户端一对多的情况)
我们可以让服务端的一个进程(线程)只负责与客户端建立连接,剩下的一批进程(线程)可以各自与一个客户端进行沟通。这样就可以达到目标。
②TCP + 多进程
首先,对于客户端的代码而言,不需要作出任何的改动!因为客户端只需要一直和服务端进行通信即可。
主要更改是在服务端,我们通过创建子进程的方式来实现职责的分离,也就是父进程值负责与客户端建立连接,而子进程负责与客户端进行收发消息。
注意:
- 子进程是拷贝父进程的PCB,因此需要父进程先与客户端建立连接,也即在父进程的PCB中的fd_array中有了该套接字的文件描述符之后再创建子进程。
- 子进程创建成功过,由于它只需要和客户端进行收发消息,因此只需要accpet返回的新套接字描述符即可,所以需要将拷贝自父进程的侦听套接字关闭。
- 客户端如果将连接关闭,则子进程需要将对应的套接字即文件描述符关闭,然后该进程需要退出。(退出时,一定要通知父进程来回收子进程的退出状态信息,否则子进程就会变成僵尸进程!但是我们不能采用wait | waitpid来回收。因为wait具有阻塞属性,而waitpid需要搭配循环来使用,均不符合预期。我们可以通过信号量的方式来处理,即改写SIGCHILD信号!)
- #include<stdio.h>
- 2 #include<string.h>
- 3 #include<sys/socket.h>
- 4 #include<arpa/inet.h>
- 5 #include<netinet/in.h>
- 6 #include<signal.h>
- 7 #include<unistd.h>
- 8 #include<sys/wait.h>
- 9
- W> 10 void signalDeal(int signum){
- 11 wait(NULL);
- 12 }
- 13 int main(){
- 14 signal(SIGCHLD, signalDeal);
- 15 int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
- 16 if(listen_fd < 0){
- 17 perror("socket");
- 18 return 0;
- 19 }
- 20 struct sockaddr_in addr;
- 21 addr.sin_family = AF_INET;
- 22 addr.sin_port = htons(22222);
- 23 addr.sin_addr.s_addr = inet_addr("0.0.0.0");
- 24 int ret = bind(listen_fd, (struct sockaddr*)&addr, sizeof(addr));
- 25 if(ret < 0){
- 26 perror("bind");
- 27 return 0;
- 28 }
- 29 ret = listen(listen_fd, 5);
- 30 if(ret < 0){
- 31 perror("listen");
- 32 return 0;
- 33 }
- 34
- 35 //收发消息
- 36 while(1){
- 37 struct sockaddr_in cli_addr;
- 38 socklen_t cli_addr_len = sizeof(cli_addr);
- 39 int new_sockfd = accept(listen_fd, (struct sockaddr*)&cli_addr,&cli_addr_len);
- 40 if(new_sockfd < 0){
- 41 perror("accept");
- 42 return 0;
- 43 }
- 44 pid_t pid = fork();
- 45 if(pid < 0){
- 46 close(new_sockfd);
- 47 continue;
- 48 }else if(pid == 0){
- 49 //子进程负责与客户端沟通
- 50 close(listen_fd);
- 51 while(1){
- 52 char buf[1024] = {0};
- 53 int recv_size = recv(new_sockfd, buf, sizeof(buf) - 1, 0);
- 54 if(recv_size < 0){
- 55 perror("recv");
- 56 continue;
- 57 }else if(recv_size == 0){
- 58 //客户端将连接关闭
- 59 printf("perr shutdown!\n");
- 60 close(new_sockfd);
- 61 return 0;
- 62 }
- 63 printf("[%s]:[%d] say [%s]\n", inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port), buf);
- 64 memset(buf, '\0', sizeof(buf));
- 65 sprintf(buf, "I am server, I recv your msg");
- 66 send(new_sockfd, buf, strlen(buf), 0);
- 67 }
- 68 }else{
- 69 close(new_sockfd);
- 70 }
- 71 }
- 72 return 0;
- 73 }
- ~
③tcp + 多线程
- 1 #include<stdio.h>
- 2 #include<pthread.h>
- 3 #include<sys/socket.h>
- 4 #include<arpa/inet.h>
- 5 #include<netinet/in.h>
- 6 #include<string.h>
- 7 #include<unistd.h>
- 8
- 9 //将新连接描述符和客户端地址信息封装传给线程入口函数
- 10 struct cli_info{
- 11 cli_info()
- 12 :_new_sockfd(-1){
- 13 memset(&addr, '0', sizeof(addr));
- 14 }
- 15 int _new_sockfd;
- 16 struct sockaddr_in addr;
- 17 };
- 18
- 19 void* tcp_deal_start(void* arg){
- 20 pthread_detach(pthread_self());
- 21 cli_info* ci = (cli_info*)arg;
- 22 int new_sockfd = ci->_new_sockfd;
- 23 struct sockaddr_in cli_addr = ci->addr;
- 24 //收发消息
- 25 while(1){
- 26 char buf[1024] = {0};
- 27 int recv_size = recv(new_sockfd, buf, sizeof(buf) - 1, 0);
- 28 if(recv_size < 0){
- 29 perror("recb");
- 30 continue;
- 31 }else if(recv_size == 0){
- 32 //客户端关闭连接
- 33 printf("perr shutdown\n");
- 34 delete ci;
- 35 close(new_sockfd);
- 36 return 0;
- 37 }
- 38 printf("[%s]:[%d] say [%s]\n", inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port), buf);
- 39 memset(buf, '\0', sizeof(buf));
- 40 sprintf(buf, "[%s] : [%d] I am serve, I recv your msg", inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port));
- 41 send(new_sockfd, buf, strlen(buf), 0);
- 42 }
- 43 }
- 44 int main(){
- 45 int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
- 46 if(listen_fd < 0){
- 47 perror("listen");
- 48 return 0;
- 49 }
- 50 struct sockaddr_in addr;
- 51 addr.sin_family = AF_INET;
- 52 addr.sin_port = htons(22222);
- 53 addr.sin_addr.s_addr = inet_addr("0.0.0.0");
- 54 int ret = bind(listen_fd, (struct sockaddr*)&addr, sizeof(addr));
- 55 if(ret < 0){
- 56 perror("bind");
- 57 return 0;
- 58 }
- 59 ret = listen(listen_fd, 5);
- 60 if(ret < 0){
- 61 perror("listen");
- 62 return 0;
- 63 }
- 64
- 65 //收发消息
- 66 while(1){
- 67 struct sockaddr_in cli_addr;
- 68 socklen_t cli_addr_len = sizeof(cli_addr);
- 69 int new_sockfd = accept(listen_fd, (struct sockaddr*)&cli_addr, &cli_addr_len);
- 70 if(new_sockfd < 0){
- 71 continue;
- 72 }
- 73 pthread_t tid;
- 74 cli_info* ci = new cli_info();
- 75 ci->_new_sockfd = new_sockfd;
- 76 memcpy(&ci->addr, &cli_addr, sizeof(cli_addr));
- 77 int ret = pthread_create(&tid, NULL, tcp_deal_start, (void*)ci);
- 78 if(ret < 0){
- 79 close(new_sockfd);
- 80 continue;
- 81 }
- 82 }
- 83 return 0;
- 84 }