tcp专题演习营之深度解析tcp/ip协议栈

大厂口试,udp不理解这些,何以过关

1. socket编程1.1 概述

TCP是TCP/IP体系中面向连接的传输层协议,它供应全双工和可靠交付的做事。
它采取许多机制来确保端到端结点之间的可靠数据传输,如采取序列号、确认重传、滑动窗口等。

php指针转整型htonslinux收集编程tcp和udp根本函数挪用进程及若何选择 Angular

首先,TCP要为所发送的每一个报文段加上序列号,担保每一个报文段能被吸收方吸收,并只被精确的吸收一次。

其次,TCP采取具有重传功能的积极确认技能作为可靠数据流传输做事的根本。
这里“确认”是指吸收端在精确收到报文段之后向发送端回送一个确认(ACK)信息。
发送方将每个已发送的报文段备份在自己的缓冲区里,而且在收到相应的确认之前是不会丢弃所保存的报文段的。
“积极”是指发送发在每一个报文段发送完毕的同时启动一个定时器,加入定时器的定时期满而关于报文段的确认信息还没有达到,则发送发认为该报文段已经丢失并主动重发。
为了避免由于网络延时引起迟到的确认和重复的确认,TCP规定在确认信息中捎带一个报文段的序号,使吸收方能精确的将报文段与确认联系起来。

末了,采取可变长的滑动窗口协议进行流量掌握,以防止由于发送端与吸收端之间的不匹配而引起的数据丢失。
这里所采取的滑动窗口协议与数据链路层的滑动窗口协议在事情事理上完备相同,唯一的差异在于滑动窗口协议用于传输层是为了在端对端节点之间实现流量掌握,而用于数据链路层是为了在相邻节点之间实现流量掌握。
TCP采取可变长的滑动窗口,使得发送端与吸收端可根据自己的CPU和数据缓存资源对数据发送和吸收能力来进行动态调度,从而灵巧性更强,也更合理。

1.2 tcp做事端编程1.2.1 TCP通信的基本步骤

做事端:socket---bind---listen---while(1){---accept---recv---send---close---}---close客户端:socket----------------------------------connect---send---recv-----------------close

1.2.2 做事器端头文件包含

#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <unistd.h>#include <string.h>#include <stdio.h>#include <stdlib.h>1.2.3 socket函数

功能:天生一个套接口描述符原型:int socket(int domain,int type,int protocol);参数:domain{ AF_INET:Ipv4网络协议 AF_INET6:IPv6网络协议}type{tcp:SOCK_STREAM udp:SOCK_DGRAM}protocol 指定socket所利用的传输协议编号,常用的协议有:IPPROTO_TCP、IPPROTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,他们分别对应TCP协议、UDP协议、STCP协议、TIPC协议,当protocol默认为0时,则根据type参数的值,自动选择协议类型;返回值:成功则返回套接口描述符,失落败返回-1。
常用实例:

int sfd = socket(AF_INET, SOCK_STREAM, 0);if(sfd == -1){ perror("socket"); exit(-1);}1.2.4 bind函数

功能:用来绑定一个端口号和IP地址,使套接口与指定的端口号和IP地址干系联。
原型:int bind(int sockfd,struct sockaddr my_addr,int addrlen);参数:sockfd为前面socket的返回值addrlen sockaddr的构造体长度。
常日是打算sizeof(struct sockaddr);my_addr为构造体指针变量对付不同的socket domain定义了一个通用的数据构造:

//此构造体不常用struct sockaddr { unsigned short int sa_family; //调用socket()时的domain参数,即AF_INET值。
char sa_data[14]; //最多利用14个字符长度};//此sockaddr构造会因利用不同的socket domain而有不同构造定义, 例如利用AF_INET domain,其socketaddr构造定义便为:struct sockaddr_in //常用的构造体{ unsigned short int sin_family; //即为sa_family AF_INET uint16_t sin_port; //为利用的port编号 struct in_addr sin_addr; //为IP 地址 unsigned char sin_zero[8]; //未利用};struct in_addr{ uint32_t s_addr;};

返回值:成功则返回0,失落败返回-1,并设置errno,最常见的errno有以下两种:

EACCES,被绑定的地址是受保护的地址,仅超级用户能够访问,比如如果绑定在1-1023端口的时候,就会报该缺点。
EADDRINUSE,被绑定的地址正在利用中,比如将socket绑定在一个处于TIME_WAIT状态的socket地址。

常用实例:

struct sockaddr_in my_addr; //定义构造体变量memset(&my_addr, 0, sizeof(struct sockaddr)); //将构造体清空//或bzero(&my_addr, sizeof(struct sockaddr));my_addr.sin_family = AF_INET; //表示采取Ipv4网络协议my_addr.sin_port = htons(8888); //表示端口号为8888,常日是大于1024的一个值。
//htons()用来将参数指定的16位hostshort转换成网络字符顺序my_addr.sin_addr.s_addr = inet_addr("192.168.0.101"); // //inet_addr()用来将IP地址字符串转换成网络所利用的二进制数字,如果为INADDR_ANY,这表示做事器自动添补本机IP地址。
if(bind(sfd, (struct sockaddr)&my_str, sizeof(struct socketaddr)) == -1) {perror("bind");close(sfd);exit(-1);}/通过将my_addr.sin_port置为0,函数会自动为你选择一个未占用的端口来利用。
同样,通过将my_addr.sin_addr.s_addr置为INADDR_ANY,系统会自动填入本机IP地址/

把稳:如果my_addr.sin_addr.s_addr = htonl(INADDR_ANY)时,INADDR_ANY便是指定地址为0.0.0.0的地址,它实在是表示不愿定地址,一样平常是用于多网卡的机器上,也便是有多个IP地址,如果设置了INADDR_ANY,那么根据端口号,无论连接哪个IP地址,都是可以连接成功的。

1.2.5 listen函数

功能:使做事器的这个端口和IP处于监听状态,等待网络中某一客户机的连接要求。
如果客户端有连接要求,端口就会接管这个连接。
原型:int listen(int sockfd, int backlog);参数:sockfd为前面socket的返回值.即sfdbacklog指定同时能处理的最大连接哀求,常日为10或者5。
最大值可设至128返回值:成功则返回0,失落败返回-1常用实例:

if(listen(sfd, 10) == -1){ perror("listen"); close(sfd); exit(-1);}1.2.6 accept函数

功能:接管远程打算机的连接要求,建立起与客户机之间的通信连接。
做事器处于监听状态时,如果某时候得到客户机的连接要求,此时并不是立即处理这个要求,而是将这个要求放在等待行列步队中,当系统空闲时再处理客户机的连接要求。
当accept函数接管一个连接时,会返回一个新的socket标识符,往后的数据传输和读取就要通过这个新的socket编号来处理,原来参数中的socket也可以连续利用,连续监听其它客户机的连接要求。
原型:int accept(int s,struct sockaddr addr,int addrlen);参数:s为前面socket的返回值,即sfdaddr为构造体指针变量,和bind的构造体是同种类型的,系统会把远程主机的信息(远程主机的地址和端口号信息)保存到这个指针所指的构造体中。
addrlen表示构造体的长度,为整型指针返回值:成功则返回新的文件描述符new_fd,失落败返回-1常用实例:

struct sockaddr_in clientaddr;memset(&clientaddr, 0, sizeof(struct sockaddr));int addrlen = sizeof(struct sockaddr);int new_fd = accept(sfd, (struct sockaddr)&clientaddr, &addrlen);if(new_fd == -1){ perror("accept"); close(sfd); exit(-1);}printf("%s %d success connect\n",inet_ntoa(clientaddr.sin_addr),ntohs(clientaddr.sin_port));

把稳:accept函数只是从监听行列步队中取出连接,而不论连接处于什么状态,网络状况有什么变革。

1.2.7 recv函数

功能:吸收远端主机传来的数据,并把数据存到由参数buf 指向的内存空间原型:int recv(int sockfd,void buf,int len,unsigned int flags);参数:sockfd为前面accept的返回值.即new_fd,也便是新的套接字。
buf表示缓冲区len表示缓冲区的长度flags常日为0返回值:>0 成功,返回实际吸收到的字符数;=0 连接关闭,解释recv在等待吸收数据时网络中断了;-1 表示出错;常用实例:

char buf[512] = {0};if(recv(new_fd, buf, sizeof(buf), 0) == -1){ perror("recv"); close(new_fd); close(sfd); exit(-1);}puts(buf);

把稳:read函数返回值不一样,大于0 是返回字节数,即是0是读到文件末端了,小于0则表示涌现了缺点,如果缺点为EINTR解释是由中断引起的,如果是ECONNNERST则表示网络连接涌现了问题。

1.2.8 send函数

功能:发送数据给指定的远端主机原型:int send(int s,const void msg,int len,unsigned int flags);参数:s为前面accept的返回值.即new_fdmsg一样平常为常量字符串len表示长度flags常日为0返回值:>0 成功,返回实际传送出去的字符数,可能会少于你所指定的发送长度;=0 连接关闭,网络中断了;-1 表示出错;常用实例:

if(send(new_fd, "hello", 6, 0) == -1){ perror("send"); close(new_fd); close(sfd); exit(-1);}

把稳:write函数返回小于0时,如果为EPIPE,则表示网络连接涌现了问题。

1.2.9 close函数

功能:当利用完文件后若已不再须要则可利用close()关闭该文件,并且close()会让数据写回磁盘,并开释该文件所占用的资源原型:int close(int fd);参数:fd为前面的sfd,new_fd返回值:若文件顺利关闭则返回0,发生缺点时返回-1常用实例:close(new_fd);close(sfd);把稳:在多进程并发做事器中,父子进程共享套接字,有多少个进程共享该套接字,该套接字就有多少个引用计数,此时个中一个进程调用close只是关闭了当提高程的这个文件描述符,但并没有发生四次挥手,直到这个套接字的引用计数为0时,才会发生四次挥手

1.2.10 sockatmark函数

功能:判断sockfd是否处于带外标记,即判断下一个读取的数据是否含有带外数据,若含有,则调用带MSG_OOB标志的recv来读取带外数据原型:int sockatmark(int sockfd);参数:fd为前面的sfd,new_fd返回值:返回1则解释下一个数据时带外数据,若返回0,则不是

1.3 tcp客户端编程1.3.1 connect函数

功能:用来要求连接远程做事器,将参数sockfd 的socket 连至参数serv_addr 指定的做事器IP和端口号上去。
原型:int connect (int sockfd,struct sockaddr serv_addr,int addrlen);参数:sockfdà为前面socket的返回值,即sfdserv_addrà为构造体指针变量,存储着远程做事器的IP与端口号信息。
addrlenà表示构造体变量的长度返回值:成功则返回0,失落败返回-1常用实例:

struct sockaddr_in seraddr;//要求连接做事器memset(&seraddr, 0, sizeof(struct sockaddr));seraddr.sin_family = AF_INET;seraddr.sin_port = htons(8888); //做事器的端口号seraddr.sin_addr.s_addr = inet_addr("192.168.0.101"); //做事器的ipif(connect(sfd, (struct sockaddr)&seraddr, sizeof(struct sockaddr)) == -1){perror("connect");close(sfd);exit(-1);}

将上面的头文件以及各个函数中的代码全部拷贝就可以形成一个完全的例子,此处省略。
还可以不写客户端程序,利用telnet远程登录来检测我们的做事器端程序。
比如我们的做事器程序在监听8888端口,我们可以用telnet 192.168.0.101 8888来查看做事真个状况。

2. tcp编程实现2.1 利用类封装tcp的基本操作

//头文件 SxTcp.h#ifndef __SXTCP_H__#define __SXTCP_H__#include <stdio.h>#define TIMEOUT_SEC 1#define MAX_READ_SIZE BUFSIZ#define DEFAULT_EPOLL_FD_NUM 1024//Tcp类class CTcp{//布局函数public: CTcp (); CTcp (int nSock); virtual ~CTcp ();//重载操作符public: int operator = (int);//赋值 int operator != (int) const;//不即是操作符 int operator == (int) const;//即是操作符//公有成员函数public: int GetHandle () const;//取出m_nSock int Open ();//创建socket int Close ();//关闭监听socket int Connect (const char , int) const;//连接(未设置超时) int Bind (const char , int) const;//绑定 int Listen (int nNum) const;//监听 int Accept () const;//接管连接 int Recv(int nFd, char buf, int nBufLen);//做事端吸收 int Send(int nFd, char buf, int nBufLen);//做事端发送 void Close (int nFd);//做事端关闭连接socket int Send (const void , int, int = TCP_TIMEOUT) const;//客户端发送数据 int Recv (void , int, int = TCP_TIMEOUT) const;//客户端吸收数据 static const int SOCK_ERROR; static const int SOCK_TIMEOUT; static const long TCP_TIMEOUT;//私有成员变量private: int m_nSock;};#endif

//sxTcp.cpp#include <stdio.h>#include <sys/socket.h>#include <unistd.h>#include <sys/types.h>#include <netinet/in.h>#include <stdlib.h>#include <time.h>#include <string.h>#include <stdarg.h>#include <arpa/inet.h>#include <iostream>#include <pthread.h>#include <sys/stat.h>#include <fcntl.h>#include <netdb.h>#include <errno.h>#include <assert.h>#include <sys/epoll.h>#include <signal.h>#include "SxTcp.h"const int CTcp::SOCK_ERROR = -100;const int CTcp::SOCK_TIMEOUT = -101;const long CTcp::TCP_TIMEOUT = 500000;//布局函数CTcp::CTcp (){ m_nSock = -1;}//布局函数CTcp::CTcp (int p_iSock){ m_nSock = p_iSock;}//析构函数CTcp::~CTcp (){ Close ();}/赋值 入参:nSockfd - socket句柄 出参:赋值后的socket句柄/int CTcp::operator = (int p_iSockfd){ //isfdtype判断nSockfd是否为指定类型,S_IFSOCK判断是否为套接字描述符,返回1是,0不是,-1出错 assert ((m_nSock == -1) && (p_iSockfd > -1) && (isfdtype (p_iSockfd, S_IFSOCK) == 1)); m_nSock = p_iSockfd; return m_nSock;}/判断两个socket句柄是否不相等 入参:n - "!="右边的socket句柄 出参:1:不相等;0:相等/int CTcp::operator != (int p_iSockfd) const{ return (m_nSock != p_iSockfd);}/判断两个socket句柄是否相等 入参:n - "=="右边的socket句柄 出参:1:相等;0:不相等/int CTcp::operator == (int p_iSockfd) const{ return (m_nSock == p_iSockfd);}/取出socket句柄 入参:无 出参:取出的socket句柄/int CTcp::GetHandle () const{ return m_nSock;}/创建socket 入参:无 出参:1: 成功 ; 0: 失落败/int CTcp::Open (){ assert (m_nSock == -1); //获取tcp套接字 m_nSock = socket (AF_INET, SOCK_STREAM, 0); return (m_nSock != -1);}/关闭socket 入参:无 出参:1: 成功 ; 0: 失落败/int CTcp::Close (){ if (m_nSock != -1) { close (m_nSock); m_nSock = -1; } return 1;}/连接(未设置超时),默认为壅塞IO 入参:pHost - IP地址或主机名 nPort - 端口 出参:1: 成功 ; 0: 失落败/int CTcp::Connect (const char p_szHost, int p_iPort) const{ assert ((m_nSock != -1) && p_szHost && (p_iPort > 0)); struct sockaddr_in addr; struct hostent phe = NULL; memset ((void)&addr, 0, sizeof (addr)); addr.sin_family = AF_INET; addr.sin_port = htons (p_iPort); if ((addr.sin_addr.s_addr = inet_addr (p_szHost)) == -1) { if ((phe = gethostbyname (p_szHost)) == NULL) return 0; memcpy ((char )&addr.sin_addr, phe->h_addr, phe->h_length); } return (connect (m_nSock, (struct sockaddr )&addr, sizeof (addr)) == 0);}/绑定 入参:pIP - IP地址 nPort - 端口 出参:1: 成功 ; 0: 失落败/int CTcp::Bind (const char pIP, int nPort) const{ assert ((m_nSock != -1) && (nPort > 0)); struct sockaddr_in addr; struct hostent phe = NULL; int opt=1; if (setsockopt (m_nSock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(int)) == -1) { return 0; } memset (&addr, 0, sizeof (addr)); addr.sin_family = AF_INET; addr.sin_port = htons (nPort); if (!pIP) { addr.sin_addr.s_addr = htonl (INADDR_ANY); } else { if ((addr.sin_addr.s_addr = inet_addr (pIP)) == -1) { if ((phe = gethostbyname (pIP)) == NULL) return 0; memcpy ((char )&addr.sin_addr, phe->h_addr, phe->h_length); } } return (bind (m_nSock, (struct sockaddr )&addr, sizeof (addr)) == 0);}/监听 入参:nNum - 监听数目 出参:1: 成功 ; 0: 失落败/int CTcp::Listen (int nNum) const{ assert ((m_nSock != -1) && (nNum > 0)); return (listen (m_nSock, nNum) == 0);}/接管连接 入参:无 出参:其他: 连接套接字句柄 ; -1: 失落败/int CTcp::Accept () const{ assert (m_nSock != -1); return (accept (m_nSock, (struct sockaddr )NULL, NULL));}/做事端吸收数据 入参:pBuf - 吸收缓存 nCount - 需吸收字节数 出参:实际吸收字节数 ,如果吸收失落败,返回负数,如果对方关闭,返回0/int CTcp::Recv(int nFd, char buf, int nBufLen){ assert(nFd != -1 ); return recv(nFd, buf, nBufLen, 0);}/客户端吸收数据 入参:pBuf - 吸收缓存 nCount - 需吸收字节数 nMicsec - socket超市价,单位:奇妙,缺省:500000奇妙 出参:实际吸收字节数 ,如果吸收失落败,返回负数,如果对方关闭,返回0/int CTcp::Recv (void pBuf, int nCount, int nMicsec) const{ assert ((m_nSock != -1) && pBuf && (nCount > 0)); int sn = 0, rn = 0; struct timeval tvlTime; fd_set rdfdset; if (nMicsec >= 0) { tvlTime.tv_sec = nMicsec / 1000000; tvlTime.tv_usec = abs (nMicsec - tvlTime.tv_sec 1000000); } FD_ZERO (&rdfdset); FD_SET (m_nSock, &rdfdset); if (nMicsec > 0) sn = select (m_nSock + 1, &rdfdset, NULL, NULL, &tvlTime); else sn = select (m_nSock + 1, &rdfdset, NULL, NULL, NULL); switch (sn) { case -1: return SOCK_ERROR; case 0: return SOCK_TIMEOUT; } if ((rn = read (m_nSock, pBuf, nCount)) < 0) return SOCK_ERROR; return rn;}/做事端发送数据入参:pBuf - 发送缓存nCount - 需发送字节数出参:实际发送字节数 ,如果发送失落败,返回负数/int CTcp::Send(int nFd, char buf, int nBufLen){ assert(nFd != -1 ); return send(nFd, buf, nBufLen, 0);}/客户端发送数据 入参:pBuf - 发送缓存 nCount - 需发送字节数 nMicsec - socket超市价,单位:奇妙,缺省:500000奇妙 出参:实际发送字节数 ,如果发送失落败,返回负数/int CTcp::Send (const void pBuf, int nCount, int nMicsec) const{ assert ((m_nSock != -1) && pBuf && (nCount > 0)); int sn = 0, wn = 0; struct timeval tvlTime; fd_set wtfdset; if (nMicsec >= 0) { tvlTime.tv_sec = nMicsec / 1000000; tvlTime.tv_usec = abs (nMicsec - tvlTime.tv_sec 1000000); } FD_ZERO (&wtfdset); FD_SET (m_nSock, &wtfdset); if (nMicsec >= 0) sn = select (m_nSock + 1, NULL, &wtfdset, NULL, &tvlTime); else sn = select (m_nSock + 1, NULL, &wtfdset, NULL, NULL); switch (sn) { case -1: return SOCK_ERROR; case 0: return SOCK_TIMEOUT; } if ((wn = send (m_nSock, pBuf, nCount, 0)) <= 0) return SOCK_ERROR; return wn;}void CTcp::Close (int nFd){ if (nFd != -1 ) { close(nFd); nFd = -1; }}

将该类编译成动态库:

g++ -g -c SxTcp.cpp -fPICg++ -g -o libnetwork.so SxTcp.o -shared2.2 利用类CTcp实现基本做事端和客户端

//做事端TestServer.cpp

//TestServer.cpp#include <stdio.h>#include <string.h>#include "../../network/SxTcp.h"int main(){ CTcp tcp; int iRet = 0; int iFd = 0; char buf[128] = {0}; iRet = tcp.Open(); if (iRet == 0) { perror("socket create failed"); return -1; } iRet = tcp.Bind("192.168.233.250", 6666); if (iRet == 0) { perror("socket bind failed"); return -1; } iRet = tcp.Listen(10); if (iRet == 0) { perror("socket listen failed"); return -1; } while(1) { memset(buf, 0, sizeof(buf)); iFd = tcp.Accept(); if (iFd == -1 ) { perror("socket accept failed"); return -1; } iRet = tcp.Recv(iFd, buf, sizeof(buf)); if (iRet < 0 ) { perror("recv failed"); tcp.Close(iFd); return -1; } else if(iRet == 0) { perror("socket not connect"); tcp.Close(iFd); return -1; } fprintf(stdout, "recv data is:%s\n", buf); memset(buf, 0, sizeof(buf)); strncpy(buf, "I have redv your data,over!", sizeof(buf)-1); iRet = tcp.Send(iFd, buf, strlen(buf)); if (iRet < 0) { perror("send failed"); tcp.Close(iFd); return -1; } else if(iRet == 0) { perror("socket not connect"); tcp.Close(iFd); return -1; } } return 0;}

//客户端

//TestClient.cpp#include <stdio.h>#include <iostream>#include <string.h>#include "../../network/SxTcp.h"using namespace std;int main(){ CTcp tcp; int iRet = 0; int iFd = 0; char buf[128] = {0}; iRet = tcp.Open(); if (iRet == 0) { perror("socket create failed"); return -1; } iRet = tcp.Connect("192.168.233.250", 6666); if (iRet == 0) { perror("socket connect failed"); return -1; } while(1) { memset(buf, 0, sizeof(buf)); cout << "please input some string:"; cin >> buf; iRet = tcp.Send(buf, strlen(buf)); if (iRet < 0 && errno != EAGAIN) { perror("send failed"); return -1; } else if(iRet == 0) { perror("connect is closed"); return -1; } memset(buf, 0, sizeof(buf)); iRet = tcp.Recv(buf, sizeof(buf)); if (iRet < 0 && errno != EAGAIN) { perror("recv failed"); return -1; } else if(iRet == 0) { perror("socket not connect"); return -1; } fprintf(stdout, "recv data is:%s\n", buf); } return 0;}

分别编译做事端和客户端,然后发送数据,会创造客户端发送完第一次后,再第二次循环中会报recv failed。
这是由于做事端壅塞在accept了,没办法第二次吸收和发送数据,那么客户端超时往后就会报错,返回负数,导致客户端退出。
当然也可以在做事真个recv和send外再加一个循环,如下:

//TestServer.cpp#include <stdio.h>#include <string.h>#include "../../network/SxTcp.h"int main(){ CTcp tcp; int iRet = 0; int iFd = 0; char buf[128] = {0}; iRet = tcp.Open(); if (iRet == 0) { perror("socket create failed"); return -1; } iRet = tcp.Bind("192.168.233.250", 6666); if (iRet == 0) { perror("socket bind failed"); return -1; } iRet = tcp.Listen(10); if (iRet == 0) { perror("socket listen failed"); return -1; } while(1) { memset(buf, 0, sizeof(buf)); iFd = tcp.Accept(); if (iFd == -1 ) { perror("socket accept failed"); return -1; } while(1){ memset(buf, 0, sizeof(buf)); iRet = tcp.Recv(iFd, buf, sizeof(buf)); if (iRet < 0 ) { perror("recv failed"); tcp.Close(iFd); return -1; } else if(iRet == 0) { perror("socket not connect"); tcp.Close(iFd); return -1; } fprintf(stdout, "recv data is:%s\n", buf); memset(buf, 0, sizeof(buf)); strncpy(buf, "I have redv your data,over!", sizeof(buf)-1); iRet = tcp.Send(iFd, buf, strlen(buf)); if (iRet < 0) { perror("send failed"); tcp.Close(iFd); return -1; } else if(iRet == 0) { perror("socket not connect"); tcp.Close(iFd); return -1; } } } return 0;}

但很显然,这样就没办法吸收到第二个连接了,那么怎么办理呢?往下面看。

3. 网络编程模式

上面的虽然可以实现多个客户端访问,但是仍旧是壅塞模式(即一个客户访问的时候会壅塞不让其余的客户访问)。
办理办法有三种,分别是多进程、多线程、异步IO。

3.1 多进程

//由于开销比较大,以是不常用

#include <stdio.h>#include <string.h>#include <sys/types.h>#include <unistd.h>#include "../../network/SxTcp.h"int main(){ CTcp tcp; int iRet = 0; int iFd = 0; char buf[128] = {0}; iRet = tcp.Open(); if (iRet == 0) { perror("socket create failed"); return -1; } iRet = tcp.Bind("192.168.233.250", 6666); if (iRet == 0) { perror("socket bind failed"); return -1; } iRet = tcp.Listen(10); if (iRet == 0) { perror("socket listen failed"); return -1; } while(1) { memset(buf, 0, sizeof(buf)); iFd = tcp.Accept(); if (iFd == -1 ) { perror("socket accept failed"); return -1; } if (fork() == 0) { while(1){ memset(buf, 0, sizeof(buf)); iRet = tcp.Recv(iFd, buf, sizeof(buf)); if (iRet < 0 ) { perror("recv failed"); tcp.Close(iFd); return -1; } else if(iRet == 0) { perror("socket not connect"); tcp.Close(iFd); return -1; } fprintf(stdout, "recv data is:%s\n", buf); memset(buf, 0, sizeof(buf)); strncpy(buf, "I have redv your data,over!", sizeof(buf)-1); iRet = tcp.Send(iFd, buf, strlen(buf)); if (iRet < 0) { perror("send failed"); tcp.Close(iFd); return -1; } else if(iRet == 0) { perror("socket not connect"); tcp.Close(iFd); return -1; } } } else { tcp.Close(iFd); } } return 0;}3.2 多线程

#include <stdio.h>#include <string.h>#include <sys/types.h>#include <unistd.h>#include <thread>#include "../../network/SxTcp.h"CTcp tcp;void ReadThread(void arg){ int pFd = (int)arg; int iFd = pFd; char buf[128] = {0}; int iRet = 0; while(1){ memset(buf, 0, sizeof(buf)); iRet = tcp.Recv(iFd, buf, sizeof(buf)); if (iRet < 0 ) { perror("recv failed"); tcp.Close(iFd); break; } else if(iRet == 0) { perror("socket not connect"); tcp.Close(iFd); break; } fprintf(stdout, "recv data is:%s\n", buf); memset(buf, 0, sizeof(buf)); strncpy(buf, "I have redv your data,over!", sizeof(buf)-1); iRet = tcp.Send(iFd, buf, strlen(buf)); if (iRet < 0) { perror("send failed"); tcp.Close(iFd); break; } else if(iRet == 0) { perror("socket not connect"); tcp.Close(iFd); break; } } }int main(){ int iRet = 0; int iFd = 0; char buf[128] = {0}; iRet = tcp.Open(); if (iRet == 0) { perror("socket create failed"); return -1; } iRet = tcp.Bind("192.168.233.250", 6666); if (iRet == 0) { perror("socket bind failed"); return -1; } iRet = tcp.Listen(10); if (iRet == 0) { perror("socket listen failed"); return -1; } while(1) { memset(buf, 0, sizeof(buf)); iFd = tcp.Accept(); if (iFd == -1 ) { perror("socket accept failed"); return -1; } std::thread t_read(ReadThread, (void)&iFd); t_read.detach(); } return 0;}3.3 异步IO

异步实在便是epoll和select模式,可以看其余的两篇专门讲epoll和select的文章。

4. 利用UDP编程4.1 UDP协议4.1.1 概述

UDP即用户数据报协议,它是一种无连接协议,因此不须要像TCP那样通过三次握手来建立一个连接。
同时,一个UDP运用可同时作为运用的客户或做事器方。
由于UDP协议并不须要建立一个明确的连接,因此建立UDP运用要比建立TCP运用大略得多。

它比TCP协议更为高效,也能更好地办理实时性的问题。
如今,包括网络视频会议系统在内的浩瀚的客户/做事器模式的网络运用都利用UDP协议。

4.1.2 Udp数据包头格式

源端口占用16bit,表示运用程序通过哪个端口来发送数据包;目的端口占用16bit,表示数据包发送给对方运用程序的哪个端口;长度占用16bit,表示包含头部在内的udp数据包的长度;校验占用16bit,用来检讨数据包是否存在差错;

4.1.3 udp基本通信流程及函数

UDP通信流程图如下:做事端:socket---bind---recvfrom---sendto---close客户端:socket----------sendto---recvfrom---closesendto()函数原型:int sendto(int sockfd, const void msg,int len,unsigned int flags,const struct sockaddr to, int tolen);该函数比send()函数多了两个参数,to表示目地机的IP地址和端口号信息,而tolen常常被赋值为sizeof (struct sockaddr)。
sendto 函数也返回实际发送的数据字节长度或在涌现发送缺点时返回-1。
recvfrom()函数原型:int recvfrom(int sockfd,void buf,int len,unsigned int flags,struct sockaddr from,int fromlen);from是一个struct sockaddr类型的变量,该变量保存连接机的IP地址及端口号。
fromlen常置为sizeof (struct sockaddr)。
当recvfrom()返回时,fromlen包含实际存入from中的数据字节数。
Recvfrom()函数返回吸收到的字节数或 当涌现缺点时返回-1,并置相应的errno。
把稳:socket编程还供应了一对函数sendmsg/recvmsg用于读写数据,该对函数既可用于tcp报文,也可用于udp报文,是通用的。

4.2 UDP编程实现

Example:

//UDP的基本操作//做事器端:#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <unistd.h>#include <string.h>#include <stdio.h>#include <stdlib.h>main(){ int sfd = socket(AF_INET, SOCK_DGRAM, 0); if(sfd == -1) { perror("socket"); exit(-1); } struct sockaddr_in saddr; bzero(&saddr, sizeof(saddr)); saddr.sin_family = AF_INET; saddr.sin_port = htons(8888); saddr.sin_addr.s_addr = INADDR_ANY; if(bind(sfd, (struct sockaddr)&saddr, sizeof(struct sockaddr)) == -1) { perror("bind"); close(sfd); exit(-1); } char buf[512] = {0}; while(1) { struct sockaddr_in fromaddr; bzero(&fromaddr, sizeof(fromaddr)); int fromaddrlen = sizeof(struct sockaddr); if(recvfrom(sfd, buf, sizeof(buf), 0, (struct sockaddr)&fromaddr, &fromaddrlen) == -1) { perror("recvfrom"); close(sfd); exit(-1); }printf("receive from %s %d,the message is:%s\n", inet_ntoa(fromaddr.sin_addr), ntohs(fromaddr.sin_port), buf); sendto(sfd, "world", 6, 0, (struct sockaddr)&fromaddr, sizeof(struct sockaddr));} close(sfd);}

//客户端:#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <unistd.h>#include <string.h>#include <stdio.h>#include <stdlib.h>int main(int argc, char argv[]){ int sfd = socket(AF_INET, SOCK_DGRAM, 0); if(sfd == -1) { perror("socket"); exit(-1); } struct sockaddr_in toaddr; bzero(&toaddr, sizeof(toaddr)); toaddr.sin_family = AF_INET; toaddr.sin_port = htons(atoi(argv[2])); //此处的端口号要跟做事器一样 toaddr.sin_addr.s_addr = inet_addr(argv[1]); //此处为做事器的ip sendto(sfd, "hello", 6, 0, (struct sockaddr)&toaddr, sizeof(struct sockaddr)); char buf[512] = {0}; struct sockaddr_in fromaddr; bzero(&fromaddr, sizeof(fromaddr)); int fromaddrlen = sizeof(struct sockaddr); if(recvfrom(sfd, buf, sizeof(buf), 0, (struct sockaddr)&fromaddr, &fromaddrlen) == -1) { perror("recvfrom"); close(sfd); exit(-1); }printf("receive from %s %d,the message is:%s\n", inet_ntoa(fromaddr.sin_addr), ntohs(fromaddr.sin_port), buf); close(sfd);}

Example2:

//UDP发送文件,先发文件大小,再发文件内容//做事器端:#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <unistd.h>#include <fcntl.h>#include <sys/stat.h>#include <string.h>#include <stdio.h>#include <stdlib.h>main(){ int sfd = socket(AF_INET, SOCK_DGRAM, 0); if(sfd == -1) { perror("socket"); exit(-1); } struct sockaddr_in saddr; bzero(&saddr, sizeof(saddr)); saddr.sin_family = AF_INET; saddr.sin_port = htons(8888); saddr.sin_addr.s_addr = INADDR_ANY; if(bind(sfd, (struct sockaddr)&saddr, sizeof(struct sockaddr)) == -1) { perror("bind"); close(sfd); exit(-1); } char buf[512] = {0}; struct sockaddr_in fromaddr; bzero(&fromaddr, sizeof(fromaddr)); int fromaddrlen = sizeof(struct sockaddr); if(recvfrom(sfd, buf, sizeof(buf), 0, (struct sockaddr)&fromaddr, &fromaddrlen) == -1) { perror("recvfrom"); close(sfd); exit(-1); } printf("receive from %s %d,the message is:%s\n", inet_ntoa(fromaddr.sin_addr), ntohs(fromaddr.sin_port), buf); FILE fp = fopen("1.txt","rb"); struct stat st; //用于获取文件内容的大小 stat("1.txt", &st); int filelen = st.st_size; sendto(sfd, (void)&filelen, sizeof(int), 0, (struct sockaddr)&fromaddr, sizeof(struct sockaddr)); while(!feof(fp)) //表示没有到文件尾 { int len = fread(buf,1,sizeof(buf),fp); sendto(sfd, buf, len, 0, (struct sockaddr)&fromaddr, sizeof(struct sockaddr));} close(sfd);}

//客户端:#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <unistd.h>#include <string.h>#include <stdio.h>#include <stdlib.h>#define BUFSIZE 512int main(int argc, char argv[]){ int sfd = socket(AF_INET, SOCK_DGRAM, 0); if(sfd == -1) { perror("socket"); exit(-1); } struct sockaddr_in toaddr; bzero(&toaddr, sizeof(toaddr)); toaddr.sin_family = AF_INET; toaddr.sin_port = htons(atoi(argv[2])); toaddr.sin_addr.s_addr = inet_addr(argv[1]); sendto(sfd, "hello", 6, 0, (struct sockaddr)&toaddr, sizeof(struct sockaddr)); char buf[BUFSIZE] = {0}; struct sockaddr_in fromaddr; bzero(&fromaddr, sizeof(fromaddr)); int fromaddrlen = sizeof(struct sockaddr); int filelen = 0; //用于保存文件长度 FILE fp = fopen("2.txt","w+b");//吸收文件的长度recvfrom(sfd, (void)&filelen, sizeof(int), 0, (struct sockaddr)&fromaddr, &fromaddrlen); printf("the length of file is %d\n",filelen); printf("Create a new file!\n"); printf("begin to reveive file content!\n"); //吸收文件的内容while(1) { int len = recvfrom(sfd, buf, sizeof(buf), 0, (struct sockaddr)&fromaddr, &fromaddrlen); if(len < BUFSIZE)//如果吸收的长度小于BUFSIZE,则表示末了一次吸收,此时要用break退出循环 { fwrite(buf,sizeof(char),len,fp); break; } fwrite(buf,sizeof(char),len,fp); } printf("receive file finished!\n"); close(sfd);}5. 协议的选择对数据哀求高可靠性的运用需选择TCP协议,如验证、密码字段的传送都是不许可出错的,而对数据的可靠性哀求不那么高的运用可选择UDP传送;TCP协议在传送过程中要利用三次握手、重传确认等手段来担保数据传输的可靠性。
利用TCP协议会有较大的时延,因此不适宜对实时性哀求较高的运用,如VOIP、视频监控等。
相反,UDP协议则在这些运用中能发挥很好的浸染;由于TCP协议的提出紧张是办理网络的可靠性问题,它通过各种机制来减少缺点发生的概率。
因此,在网络状况不是很好的情形下需选用TCP协议(如在广域网等情形),但是若在网络状况很好的情形下(如局域网等)就不须要再采取TCP协议,而建议选择UDP协议来减少网络负荷;