目录
网络初识
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、代码
协议:约定沟通双方传递信息的格式
网络完成的事:将数据从主机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
2 #include
3 #include
4 #include
5 #include
6 #include
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
2 #include
3 #include
4 #include
5 #include
6 #include
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
2 #include
3 #include
4 #include
5 #include
6 #include
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
2 #include
3 #include
4 #include
5 #include
6 #include
7 #include
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
2 #include
3 #include
4 #include
5 #include
6 #include
7 #include
8 #include
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
2 #include
3 #include
4 #include
5 #include
6 #include
7 #include
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 }