目录
一丶概念梳理
1.源IP和目的IP
?2.源端口和目的端口
3.通信识别
?4.UDP协议和TCP协议
1.UDP协议特点
2.TCP协议特点
?5.网络字节序
1.大端字节序与小端字节序
2.字节序
?6.地址转换函数
inet_aton函数
inet_ntoa函数
inet_addr函数
二丶socket编程接口
1.socket常见API
?1.创建套接字:
2.绑定端口号
3.监听套接字
4. 接受请求
5.建立连接
2.套接字地址结构
?编辑
三丶TCP网络程序
?编辑
1.TCP客户端
2.?TCP服务端
1.多进程版本
2.多线程版本
一丶概念梳理
1.源IP和目的IP
之前学习到的两个计算机之间想要进行通信,就要定一种协议但是这些都是基于知道对方主机的IP和端口号的。两个主机都需要知道对方的IP~
?2.源端口和目的端口
想要进行主机与主机之间的通信工作必须要知道彼此之间的IP和端口号缺一不可,那么端口号的作用就是对方主机哪个程序的唯一标识·也被称之为程序地址~
对于源IP地址和目的IP地址,就是确定了哪两台主机要通信;
对于源端口号和目的端口号,就是确定了两台主机上的哪两个进程要进行通信;
3.通信识别
通过IP+端口号和通信协议就可以确定一个网络通信~
- IP地址最大的意义在于指导一个报文该如何进行路径选择,到哪里去就是去找目标IP地址。
- 端口号的意义在于唯一的标识一台机器上的唯一一个进程。
- IP地址 + 端口号 = 能够标识互联网中的唯一一个进程!
- IP地址 + port(端口号) = ?socket(套接字)
进程ID与端口号的理解:
? ? ? ? 每个进程都需要有自己的PID用来标识,但是有的进程是系统进程,有的是网络进程。所以说不是所有的进程都需要端口号。但是一个进程可以绑定多个端口号。但是一个端口号不能被多个进程绑定~
?4.UDP协议和TCP协议
1.UDP协议特点
UDP是 User Datagram Protocol 的缩写,即用户数据报协议。
? ? ? ? UDP不提供复杂的控制机制,利用IP提供面向无连接的通信服务。并且它是将应用程序发来的数据在收的那一刻,立即按照原样发送到网络上的一种机制。
2.TCP协议特点
TCP是 Transmission Control Protocol 的缩写,即传输控制协议。
? ? ? ? TCP与UDP的区别相当大。它充分地实现了数据传输时的各种控制功能,可以进行丢包时重发控制,还可以对次序乱掉的分包进行顺序控制。而这些再UDP中都没有。此外,TCP作为一种面向有连接的协议,只要在确认通信对端存在时才会发生数据,从而可以控制通信流量的浪费。
总之UDP是无连接通信,TCP是有连接通信。无连接通信特点在于客户端只需给用户提供服务,不用关心提供服务之后的事情
有连接通信特点在于给用户提供过服务之后。需要关心后续的状态~?
?5.网络字节序
1.大端字节序与小端字节序
- 大端模式:?数据的高字节内容保存在内存的低地址处,数据的低字节内容保存在内存的高地址处。
- 小端模式:?数据的高字节内容保存在内存的高地址处,数据的低字节内容保存在内存的低地址处。
2.字节序
????????与同一台计算机上的进程进行通信时,一般不考虑字节序。字节序是一个处理器架构特性,用于指示像整数这样的大数据类型内部的字节如何排序。但如果涉及网络通信,那就必须考虑大小端的问题,否则对端主机识别出来的数据可能与发送端想要发送的数据是不一致的。
? ? ? ? TCP/IP协议栈使用大端字节序。应用程序交换格式化数据时,字节序问题就会出现。对TCP/IP,地址用网络字节序来表示,所以应用程序有时候需要在处理器的字节序与网络字节序之间进行转换。以确保数据的一致性。
? ? ? ? 对于TCP/IP应用程序,有四个用来在处理器字节序和网络字节序之间实施转换的函数。
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong); //返回值:以网络字节序表示的32位整数
uint16_t htons(uint16_t hostshort); //返回值:以网络字节序表示的16位整数
uint32_t ntohl(uint32_t netlong); //返回值:以主机字节序表示的32位整数
uint16_t ntohs(uint16_t netshort); //返回值:以主机字节序表示的16位整数
h表示“主机”字节序,n表示“网络”字节序。
l表示“长”整数,s表示“短”整数。
?6.地址转换函数
?????????有时候,我们需要打印出被人能理解的地址格式(如:12.0.0.1)而不是被计算机理解的地址格式(32位二进制数),那么就需要用到以下函数。
? ? ? ? 它们在ASCII字符串(这是人们偏爱使用的格式)与网络字节序的二进制值(这是存放在套接字地址结构中的值)之间转换网际地址。
#include <arpa/inet.h>
int inet_aton(const char* strptr, struct in_addr* addrptr);
in_addr_t inet_addr(const char* strptr);
char* inet_ntoa(struct in_addr addrptr);
inet_aton函数
????????该函数将strptr所指C字符串转换成一个32位的网络字节序二进制值,并且通过指针addrptr来存储。若成功则返回1,否则返回0;
? ? ? ? 如果addrptr指针为空,那么该函数仍然对输入的字符串执行有效检查,但不存储任何结果。
inet_ntoa函数
????????该函数讲一个32位的网络字节序二进制IPV4地址转换成相应的点分十进制数串。由该函数的返回值所指向的字符串驻留在静态内存中。
inet_addr函数
????????与inet_aton一样进行相同的转换,返回值为32的网络字节序二进制值。
二丶socket编程接口
1.socket常见API
//创建套接字
int socket(int domain, int type, int protocol);
//绑定端口
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
//监听套接字
int listen(int sockfd, int backlog);
//接受请求
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
//建立连接
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
?1.创建套接字:
// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);
想要进行网络编程就必须创建一个套接字。打开网络文件,
domain:
? ? ? ? 指明协议族,即你想要使用什么协议(IPV4、IPV6...),它是下列表格中的某个常值。该参数也往往被称为协议域。
domain | 功能 | ?AF_INET | IPV4协议 | ??AF_INET6 | IPV6协议 | AF_LOCAL | Unixt域协议 | AF_ROUTE | 路由套接字 | AF_KEY | 密钥套接字 |
type:
? ? ? ??
type | 功能 | SOCK_STREAM | 字节流套接字 | SOCK_DGRAM | 数据报套接字 | SOCK_SEQPACKET | 有序分组套接字 | SOCK_RAW | 原始套接字 |
?????????如果你是要TCP通信的话,就要是要SOCK_STREAM作为类型,UDP就使用SOCK_DGRAM作为类型。
? ? ? ? protocol参数:创建套接字的协议类别。你可以指明为TCP或UDP,但该字段一般直接设置为0就可以了,设置为0表示的就是默认,此时会根据传入的前两个参数自动推导出你最终需要使用的是哪种协议。 ? ? ? ??
protocol | 说? ? 明 |
---|
IPPROTO_TCP | TCP传输协议 | IPPROTO_UDP | UDP传输协议 | IPPROTO_SCTP | SCTP传输协议 |
返回值说明:
????????套接字创建成功返回一个文件描述符,创建失败返回-1,同时错误码会被设置。
2.绑定端口号
// 绑定端口号 (TCP/UDP, 服务器)
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
bind函数是把一个协议地址赋予一个套接字。
参数说明:
? ? ? ? sockfd参数:绑定的文件的文件描述符。也就是我们创建套接字时获取到的文件描述符。
? ? ? ? addr参数:这个参数是指向一个特定于协议的地址结构的指针。里面包含了协议族、端口号、IP地址等。(见下一节sockaddr结构中的介绍)
? ? ? ? addrlen参数:是该协议的地址结构的长度。
返回值说明:
? ? ? ? 绑定成功返回0,绑定失败返回-1,同时错误码会被设置。
3.监听套接字
// 开始监听socket (TCP, 服务器)
int listen(int sockfd, int backlog);
listen函数仅由TCP服务器调用,表明服务器对外宣告它愿意接受连接请求,它做两件事:
? ? ? ? 1.当socket函数创建一个套接字时,它被假设为一个主动套接字,也就是说将调用connect发起连接的客户端套接字。listen函数把一个未连接的套接字转换成一个被动套接字,指示内核应接受指向该套接字的连接请求。简单来说,服务器调用listen函数,就是告诉客户端你可以连接我了。
? ? ? ? 2.第二个参数规定了内核应该为相应的套接字排队的最大连接个数。backlog提供了一个提示,提示系统该进程要入队的未完成连接的请求数量。其实际由系统决定,对于TCP而言,默认是128。
? ? ? ? 一旦队列满了,系统就会拒绝多余的连接请求,所有backlog的值应该基于服务器期望负载和处理量来选择,其中处理量是指接受连接请求与启动服务的数量。
? ? ? ? 一旦服务器调用了listen,所用的套接字就能接受连接请求。使用accept函数获得的连接请求并建立连接。
返回值:成功返回0,失败返回-1;
? ? ? ? 本函数通常应该在调用socket和bind这两个函数之后,并在调用accept函数之前调用。
4. 接受请求
// 接收请求 (TCP, 服务器)
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
????????accept函数是由TCP服务器调用,用于从已完成连接队列队头返回下一个已完成连接。如果已完成连接队列为空,那么进程将被投入睡眠。?
? ? ? ? 如果accept成功,那么其返回值是由内核自动生成的一个全新描述符,代表与所返回客户的TCP连接。我们常常称它的第一个参数为监听套接字(listening socket) 描述符(由socket创建,随后用作bind和listen的第一个参数的描述符),称它的返回值为已连接套接字(connected socket) 描述符。区分这两个套接字非常重要。一个服务器通常仅仅创建一个监听套接字,它在该服务器的生命期内一直存在。内核为每个由服务器进程接受的客户连接创建一个已连接套接字(也就是说对于它的TCP三路握手过程已经完成)。当服务器完成对某个给定客户的服务时,相应的已连接套接字就被关闭。
? ? ? ? 总的来说,函数accept所返回的文件描述符是新的套接字描述符,该描述符连接到调用connect的客户端。这个新的套接字描述符和原始套接字(sockfd)具有相同的套接字类型和地址族。传给accept的原始套接字没有关联到这个连接,而是继续保持监听状态并接受其他连接请求。
5.建立连接
// 建立连接 (TCP, 客户端)
#included <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
????????TCP客户用connect函数来建立与TCP服务器的连接。
参数说明:
? ? ? ? sockfd参数:是由socket函数返回的套接字描述符,第二个以及第三个参数分别是指向套接字地址结构的指针和该结构的大小。
? ? ? ? 在connect中指定的地址是我们想要与之通信服务器地址。如果sockfd没有绑定到一个地址,connect会给调用者绑定一个默认地址。
返回值说明:
? ? ? ? 若成功则为0,若出错则为-1;
2.套接字地址结构
我们一般都用IPV4套接字地址结构进行绑定。?
三丶TCP网络程序
1.TCP客户端
#include<iostream>
#include<string>
#include<unistd.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<string.h>
#include<sys/types.h>
#include<arpa/inet.h>
using namespace std;
void Usage(string proc)
{
cout<<"Usage "<<proc<<" server ip server port"<<endl;
exit(1);
}
int main(int argc,char *argv[])
{
if(argc!=3)
{
Usage(argv[0]);
}
//客户端也创建套接字,打开网络文件~
int sock=socket(AF_INET,SOCK_STREAM,0);
if(sock<0)
{
cout<<"sock error"<<endl;
exit(2);
}
//客户端先设定要连接的ip port
//serip服务端ip地址。在输入的时候是字符串类型。
string serip=argv[1];
//port端口类型需要转换成整型的。然后再转成uint16_t类型~
//这里注意的是客户端不需要绑定和监听。只需要连接上服务器即可。
//使用connect将创建好的套接字连接上服务器。连接服务器我们就需要服务器的ip和端口号
//那么就需要这边也创建一个IPV4套接字地址结构。struct sockaddr_in.协议家族。
//这里ip地址和端口号需要进行特殊处理
//将主机序列转为网络序列。ip地址通过inet_addr函数进行字符串转换成一个32位的网络字节序二进制~
uint16_t serport=(uint16_t)atoi(argv[2]);
struct sockaddr_in server;
server.sin_family=AF_INET;
server.sin_port=htons(serport);
server.sin_addr.s_addr=inet_addr(serip.c_str());
if(connect(sock,(struct sockaddr*)&server,sizeof(server))<0)
{
cout<<"connect error"<<endl;
exit(3);
}
//进行业务请求
while(1)
{
cout<<"please enter# ";
char buffer[1024];
//从键盘读取数据
fgets(buffer,sizeof(buffer)-1,stdin);
//写入到套接字中。
write(sock,buffer,strlen(buffer)-1);
//从套接字中读取服务端发过来的反馈信息~
ssize_t s=read(sock,buffer,sizeof(buffer)-1);
if(s>0)
{
buffer[s]=0;
cout<<"server echp# "<<buffer<<endl;
}
}
return 0;
}
2.?TCP服务端
?
#include<iostream>
#include<string>
#include<unistd.h>
#include<netinet/in.h>
#include <sys/socket.h>
#include<string.h>
#include <sys/types.h>
#include<arpa/inet.h>
using namespace std;
void Usage(string proc)
{
cout<<"Usage "<<proc<<" server port"<<endl;
exit(1);
}
int main(int argc,char*argv[])
{
if(argc!=2)
{
Usage(argv[0]);
}
//创建网络文件tcp和udp的不同在于创建的的协议类型不同。tcp
//创建SOCK_STREAM类型。
int sock=socket(AF_INET,SOCK_STREAM,0);
if(sock<0)
{
cout<<"socket error"<<endl;
exit(2);
}
//设置协议家族,ip port端口号
//1.协议家族AF_INET,
//2.ip地址。因为使用的是云服务器,不需要显示绑定ip地址,
//直接将ip地址设置为INADDR_ANY.此时服务器可以从本地一张网卡当中读取数据。
//又因为INADDR_ANY本质为0.所以也不需要进行网络字节序转换~
//3.绑定端口号,argv[1]命令行第二个参数,将主机序列转换为网络序列~
struct sockaddr_in local;
memset(&local,0,sizeof(local));
local.sin_family=AF_INET;
local.sin_port=htons(atoi(argv[1]));
local.sin_addr.s_addr=INADDR_ANY;
if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
{
cout<<"bind error"<<endl;
exit(3);
}
//设置套接字为监听状态。就是我们刚刚创建好的套接字。
//listen作用就是一直检测是否有客户端来连接服务端~
//backlog可以当最大监听数~
int backlog=5;
if(listen(sock,backlog)<0)
{
cout<<"listen error"<<endl;
exit(4);
}
while(1)
{
//可以进行服务,accept从刚才创建的sock套接字中获取新连接。
//将获取的新连接的属性,都保存在struct sockaddr_in peer中
struct sockaddr_in peer;
socklen_t len=sizeof(peer);
int newsock=accept(sock,(struct sockaddr*)&peer,&len);
//如果一个接收失败了,就继续去接受其他的请求。失败情况:
//客户端有可能在刚连接上又退出了~
if(newsock<0)
{
continue;
}
uint16_t cliport=ntohs(peer.sin_port);
string cliip=inet_ntoa(peer.sin_addr);
cout<<"get a new link ->:["<<cliip<<";"<<cliport<<"]:"<<newsock<<endl;
//来到这里就表示监听到并且已经接受了客户端的请求。
while(1)
{
//设置一个缓冲区来存放客户发送过来的数据~
char buffer[1024];
memset(buffer,0,sizeof(buffer));
//从套接字中读取客户端的数据
ssize_t s=read(newsock,buffer,sizeof(buffer)-1);
if(s>0)
{ //客户端发送过来的就是向文件中写入的。并没有\0标识。
//服务端在读取过之后可以将这些命令整理成一个字符串命令~
buffer[s]=0;
cout<<"client #"<<buffer<<endl;
string echo=">>>server<<<";
echo+=buffer;
//再将读取到客户端的内容加加工一下返回给客户~表示服务端已经处理过了~
//向套接字中写入~
write(newsock,echo.c_str(),echo.size()-1);
}
else if(!s)
{
cout<<"client quit"<<endl;
break;
}
else
{
cout<<"read error"<<endl;
break;
}
}
}
return 0;
}
以上为单进程版本。
1.多进程版本
#include <iostream>
#include <string>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include<sys/wait.h>
#include <string.h>
#include <sys/types.h>
#include <arpa/inet.h>
using namespace std;
void Server(int newsock)
{
while (1)
{
//设置一个缓冲区来存放客户发送过来的数据~
char buffer[1024];
memset(buffer, 0, sizeof(buffer));
//从套接字中读取客户端的数据
ssize_t s = read(newsock, buffer, sizeof(buffer) - 1);
if (s > 0)
{ //客户端发送过来的就是向文件中写入的。并没有\0标识。
//服务端在读取过之后可以将这些命令整理成一个字符串命令~
buffer[s] = 0;
cout << "client #" << buffer << endl;
string echo = ">>>server<<<";
echo += buffer;
//再将读取到客户端的内容加加工一下返回给客户~表示服务端已经处理过了~
//向套接字中写入~
write(newsock, echo.c_str(), echo.size() - 1);
}
else if (!s)
{
cout << "client quit" << endl;
break;
}
else
{
cout << "read error" << endl;
break;
}
}
}
void Usage(string proc)
{
cout << "Usage " << proc << " server port" << endl;
exit(1);
}
int main(int argc, char *argv[])
{
if (argc != 2)
{
Usage(argv[0]);
}
//创建网络文件tcp和udp的不同在于创建的的协议类型不同。tcp
//创建SOCK_STREAM类型。
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0)
{
cout << "socket error" << endl;
exit(2);
}
//设置协议家族,ip port端口号
// 1.协议家族AF_INET,
// 2.ip地址。因为使用的是云服务器,不需要显示绑定ip地址,
//直接将ip地址设置为INADDR_ANY.此时服务器可以从本地一张网卡当中读取数据。
//又因为INADDR_ANY本质为0.所以也不需要进行网络字节序转换~
// 3.绑定端口号,argv[1]命令行第二个参数,将主机序列转换为网络序列~
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(atoi(argv[1]));
local.sin_addr.s_addr = INADDR_ANY;
if (bind(sock, (struct sockaddr *)&local, sizeof(local)) < 0)
{
cout << "bind error" << endl;
exit(3);
}
//设置套接字为监听状态。就是我们刚刚创建好的套接字。
// listen作用就是一直检测是否有客户端来连接服务端~
// backlog可以当最大监听数~
int backlog = 5;
if (listen(sock, backlog) < 0)
{
cout << "listen error" << endl;
exit(4);
}
while (1)
{
//可以进行服务,accept从刚才创建的sock套接字中获取新连接。
//将获取的新连接的属性,都保存在struct sockaddr_in peer中
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int newsock = accept(sock, (struct sockaddr *)&peer, &len);
//如果一个接收失败了,就继续去接受其他的请求。失败情况:
//客户端有可能在刚连接上又退出了~
if (newsock < 0)
{
continue;
}
uint16_t cliport = ntohs(peer.sin_port);
string cliip = inet_ntoa(peer.sin_addr);
cout << "get a new link ->:[" << cliip << ";" << cliport << "]:" << newsock << endl;
//来到这里就表示监听到并且已经接受了客户端的请求。
//多进程版本。创建一个子进程
pid_t id = fork();
if (id < 0)
{
continue;
}
//子进程去执行服务
else if (id == 0)
{
//这里注意创建的子进程会进程父进程一开始打开的sock套接字。和获取新连接后的newsock套接字
//但是这里我们不希望子进程去改变一开始创建的sock套接字。所以子进程在被创建后就去关闭
//继承下来的sock
close(sock);
//这里我们继续对子进程创建子进程,并且让子进程退出,那么留下来的就是孙子进程。如果不创建孙子进程,
//父进程需要关心子进程的退出状态。阻塞和非阻塞都不是很好的选择。孙子进程和爷爷进程之间不存在任何联系。
//孙子进程在执行结束之后由操作系统领养。
if (fork() > 0)
exit(0);
Server(newsock);
close(newsock);
exit(0);
}
else
{
//父进程等待被结束进程的id
waitpid(id,nullptr,0);
close(newsock);
}
}
return 0;
}
2.多线程版本
#include <iostream>
#include <string>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include<sys/wait.h>
#include<pthread.h>
#include <string.h>
#include <sys/types.h>
#include <arpa/inet.h>
using namespace std;
void Server(int newsock)
{
while (1)
{
//设置一个缓冲区来存放客户发送过来的数据~
char buffer[1024];
memset(buffer, 0, sizeof(buffer));
//从套接字中读取客户端的数据
ssize_t s = read(newsock, buffer, sizeof(buffer) - 1);
if (s > 0)
{ //客户端发送过来的就是向文件中写入的。并没有\0标识。
//服务端在读取过之后可以将这些命令整理成一个字符串命令~
buffer[s] = 0;
cout << "client #" << buffer << endl;
string echo = ">>>server<<<";
echo += buffer;
//再将读取到客户端的内容加加工一下返回给客户~表示服务端已经处理过了~
//向套接字中写入~
write(newsock, echo.c_str(), echo.size() - 1);
}
else if (!s)
{
cout << "client quit" << endl;
break;
}
else
{
cout << "read error" << endl;
break;
}
}
}
void*Rounite(void*args)
{
pthread_detach(pthread_self());
int sock = *(int*)args;
delete (int*)args;
Server(sock);
close(sock);
return 0;
}
void Usage(string proc)
{
cout << "Usage " << proc << " server port" << endl;
exit(1);
}
int main(int argc, char *argv[])
{
if (argc != 2)
{
Usage(argv[0]);
}
//创建网络文件tcp和udp的不同在于创建的的协议类型不同。tcp
//创建SOCK_STREAM类型。
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0)
{
cout << "socket error" << endl;
exit(2);
}
//设置协议家族,ip port端口号
// 1.协议家族AF_INET,
// 2.ip地址。因为使用的是云服务器,不需要显示绑定ip地址,
//直接将ip地址设置为INADDR_ANY.此时服务器可以从本地一张网卡当中读取数据。
//又因为INADDR_ANY本质为0.所以也不需要进行网络字节序转换~
// 3.绑定端口号,argv[1]命令行第二个参数,将主机序列转换为网络序列~
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(atoi(argv[1]));
local.sin_addr.s_addr = INADDR_ANY;
if (bind(sock, (struct sockaddr *)&local, sizeof(local)) < 0)
{
cout << "bind error" << endl;
exit(3);
}
//设置套接字为监听状态。就是我们刚刚创建好的套接字。
// listen作用就是一直检测是否有客户端来连接服务端~
// backlog可以当最大监听数~
int backlog = 5;
if (listen(sock, backlog) < 0)
{
cout << "listen error" << endl;
exit(4);
}
while (1)
{
//可以进行服务,accept从刚才创建的sock套接字中获取新连接。
//将获取的新连接的属性,都保存在struct sockaddr_in peer中
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int newsock = accept(sock, (struct sockaddr *)&peer, &len);
//如果一个接收失败了,就继续去接受其他的请求。失败情况:
//客户端有可能在刚连接上又退出了~
if (newsock < 0)
{
continue;
}
uint16_t cliport = ntohs(peer.sin_port);
string cliip = inet_ntoa(peer.sin_addr);
cout << "get a new link ->:[" << cliip << ";" << cliport << "]:" << newsock << endl;
//来到这里就表示监听到并且已经接受了客户端的请求。
//多线程版本
pthread_t tid;
int*pram=new int(newsock);
pthread_create(&tid,nullptr,Rounite,pram);
|