#include
#include
#include
#include
#include
#include
#include
#include
int main()
{
char buf[1024];
struct sockaddr_in servaddr,cliaddr;//定义服务器地址结构体,和客户端地址结构体
socklen_t len=sizeof(cliaddr);//得到客户端结构体的大小
int listenfd=socket(PF_INET,SOCK_STREAM,0);//创建套接字
int flag = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag));//设置套接字
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family=AF_INET;
servaddr.sin_port=htons(atoi("8888"));//设置端口号
servaddr.sin_addr.s_addr=htonl(INADDR_ANY);//设置服务器地址,这里的宏表示包含回环地址,和网路ip地址;
bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr));//绑定套接字,也就是设置套接字的属性
listen(listenfd,5);//将套接字由主动发送状态变为被动接收状态;,并设置请求队列中允许的最大请求数,大多数系统默认为5
while(1){
int connfd=accept(listenfd,(struct sockaddr *)&cliaddr,&len);//阻塞等待客户端的连接;直到请求队列中出现请求
//获取可用于传输的套接字;
strcpy(buf,"hello ,I am server\n");
send(connfd,buf,1024,0);//通过connfd将缓冲区的发送数据到客户端
memset(buf,0,sizeof(buf));//清空缓冲区
recv(connfd,buf,1024,0);//
puts(buf);
close(connfd); //关闭套接字
}
close(listenfd);//关闭监听套接字
exit(0);
}
#include
#include
#include
#include
#include
#include
#include
#include
int main()
{
char buf[1024]; //创建缓冲区
struct sockaddr_in servaddr;//定义服务器地址结构体
int sockfd=socket(PF_INET,SOCK_STREAM,0);//创建套接字
bzero(&servaddr,sizeof(servaddr)); //清空服务器结构体地址内容
servaddr.sin_family=AF_INET; //设置协议族
servaddr.sin_port=htons(atoi("8888")); //设置服务器端口号
servaddr.sin_addr.s_addr=inet_addr("192.168.0.120");//设置服务器地址
connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr));//连接服务器
memset(buf,0,sizeof(buf));//清空缓冲区
recv(sockfd,buf,1024,0);//接收服务器发送的数据
puts(buf);
memset(buf,0,sizeof(buf));//清空缓冲区
strcpy(buf,"hello,I am client");//填充缓冲区数据
send(sockfd,buf,1024,0);//向服务器发送数据
close(sockfd);//关闭套接字
exit(0);
}
第一次握手客户端向服务器发带有SYN =1 ,seq=x(随机序列号) 的请求数据包
第二次握手:收到数据包之后服务器端向客户端发送一个带有SYN=1,ACK=1+x,seq=y的响应数据报包
第三次握手:客户端向服务器发送一个带有ACK=1+y,seq= x+1的响应数据报
至此三次握手建立连接成功;
首先要直到Linux内核协议栈为TCP连接管理使用两个队列:
半连接队列:用来保存SYN_SENT和SYN_RECV两个连接状态
全连接队列:用来保存ESTABLESHED状态的连接,也就是说全连接队列存放3次握手成功的连接;
而一般来说accept的作用就是阻塞等待全连接队列不为空,然后从全连接队列中取出全连接状态的套接字;当然accept阻塞或不阻塞,需要由套接字的属性决定。
而listen()第二个参数,实际上就是指定的全连接队列最大可以存放的未取出的全连接数量;如果如果accept()不去取出里面的全连接,那么当全连接队列中全连接的数量达到设置的最大值,将不能再放入全连接。
accept()可以将获取对端建立全连接的,IP,端口号,协议等信息;实际上accpet本身和三次握手没有任何关系,起到的作用就是取出全连接队列中的一个全连接,获取其属性罢了。
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
第一个参数:套接字
第二个参数:协议层SOL_SOCKET、IPPROTO_IP、IPPROTO_TCP
第三个参数:需设置的选项如:
SOL_SOCKET
SO_BROADCAST 允许发送广播数据 int
SO_DEBUG 允许调试 int
SO_DONTROUTE 不查找路由 int
SO_ERROR 获得套接字错误 int
SO_KEEPALIVE 保持连接 int
SO_LINGER 延迟关闭连接 struct linger
SO_OOBINLINE 带外数据放入正常数据流 int
SO_RCVBUF 接收缓冲区大小 int
SO_SNDBUF 发送缓冲区大小 int
SO_RCVLOWAT 接收缓冲区下限 int
SO_SNDLOWAT 发送缓冲区下限 int
SO_RCVTIMEO 接收超时 struct timeval
SO_SNDTIMEO 发送超时 struct timeval
SO_REUSERADDR 允许重用本地地址和端口 int
SO_TYPE 获得套接字类型 int
SO_BSDCOMPAT 与BSD系统兼容 int
IPPROTO_IP
IP_HDRINCL 在数据包中包含IP首部 int
IP_OPTINOS IP首部选项 int
IP_TOS 服务类型
IP_TTL 生存时间 int
IPPRO_TCP
TCP_MAXSEG TCP最大数据段的大小 int
TCP_NODELAY 不使用Nagle算法 int
第4个参数:用来设置第三参数的选项;传入类型为指针,指向存放选项值的缓冲区
第5个参数: 第4个参数地址中存放数据类型的大小
作用:设置套接字特性
level =SOL_SOCKET
optname =REUSERADDR
如果在已经处于 ESTABLISHED状态下的socket(一般由端口号和标志符区分)调用close(socket)(一般不会立即关闭而经历TIME_WAIT的过程)后想继续重用该socke可以如下设置
int reuse=1;
setsockopt(s,SOL_SOCKET ,SO_REUSEADDR,&reuse,sizeof(int));
PS:当我们设置地址重用,需要在bin()之前设置
level=SOL_SOCKET
optname=SO_REUSEADDR
如果要已经处于连接状态的soket在调用close(socket)后强制关闭,不经历TIME_WAIT的过程:
int reuse=0;
setsockopt(s,SOL_SOCKET ,SO_REUSEADDR,(const char*)& reuse,sizeof(int));
level=SOL_S0CKET,
optname=SO_SNDTIMEO 和 SO_RCVTIMEO
// 发送时限
int nNetTimeout=1000; // 1秒
setsockopt(socket,SOL_S0CKET, SO_SNDTIMEO,(char *)&nNetTimeout,sizeof(int));
// 接收时限
int nNetTimeout=1000; // 1秒
setsockopt(socket,SOL_S0CKET, SO_RCVTIMEO,(char *)&nNetTimeout,sizeof(int));
在send()的时候,返回的是实际发送出去的字节(同步)或发送到socket缓冲区的字节(异步),系统默认的状态发送和接收一次为8688字节(约为8.5K);在实际的过程中发送数据和接收数据量比较大,可以设置socket缓冲区,而避免了send(),recv()不断的循环收发:
level=SOL_S0CKET
optname=SO_RCVBUF 和SO_SNDBUF
// 接收缓冲区
int nRecvBuf=32*1024; // 设置为32K
setsockopt(s,SOL_SOCKET,SO_RCVBUF,(const char*)&nRecvBuf,sizeof(int));
// 发送缓冲区
int nSendBuf=32*1024; // 设置为32K
setsockopt(s,SOL_SOCKET,SO_SNDBUF,(const char*)&nSendBuf,sizeof(int));
注意:并不是说你设置的多大,系统就会设置多大,系统一般会将我们设置的缓冲区大小加倍,并且不得小于tcp的接收缓冲区和发送缓冲区设置的默认最小值。 注意:TCP有发送缓冲区和接收缓冲区,但是UDP因为是不可靠的,它没有确认重传机制,不保存应用程序数据的副本,所以是没有发送缓冲区的,但是UDP有接收缓冲区。
level=SOL_S0CKET
optname=SO_SNDBUF
int nZero=0;
setsockopt(socket,SOL_SOCKET,SO_SNDBUF,(char *)&nZero,sizeof(int));
level=SOL_S0CKET
optname=SO_RCVBUF
int nZero=0;
setsockopt(socket,SOL_SOCKET,SO_RCVBUF,(char *)&nZero,sizeof(int));
level=SOL_S0CKET
optname=SO_BROADCAST
int bBroadcast = 1;
setsockopt(s, SOL_SOCKET, SO_BROADCAST, (const char*)&bBroadcast, sizeof(int));
level=SOL_S0CKET
optname=SO_KEEPALIVE
int opt = 1;
if (setsockopt (m_nSock, SOL_SOCKET, SO_KEEPALIVE, &opt, sizeof(int)) == -1)
{
return 0;
}
level=SOL_TCP
optname=DEFER_ACCEPT
打开这个功能后,内核在val时间之内还没有收到数据,不会继续唤醒进程,而是直接丢弃连接。 从三次握手上讲,就是设置这个状态之后,就算完成了三次握手,服务器socket状态也不是ESTABLISHED,而依然是 SYN_RCVD,不会去接收数据。
int val = 5;
setsockopt(fd, SOL_TCP, TCP_DEFER_ACCEPT, &val, sizeof(val));
优雅关闭:如果发送缓存中还有数据未发出则将其发出去,并且收到所有数据的ACK之后,发送FIN包,开始关闭过程。TCP连接线关闭一个方向,此时另外一个方向还是可以正常进行数据传输。
强制关闭:如果缓存中还有数据,则这些数据都将被丢弃,然后发送RST包,直接重置TCP连接。两边都关闭了,服务端处理完的信息没有正常传给客户端。
#include
int close(int fd);
close()函数会使套接字的引用计数减1,若套接字的引用计数为0,则彻底关闭连接,并且会关闭TCP两个方向的数据流,既不能发送数据也不能接收数据。close函数不能关闭一个方向上的连接,而shutdown函数可以实现。如果服务端还有没有处理完的数据,不能正常发送给客户端。
int shutdown(int sock, int howto);
参数howto的取值:
SHUT_RD:断开输入流。套接字无法接收数据(即使输入缓冲区收到数据也被抹去),无法调用输入相关函数。
SHUT_WR:断开输出流。套接字无法发送数据,但如果输出缓冲区中还有未传输的数据,则将传递到目标主机。
SHUT_RDWR:同时断开 I/O 流。相当于分两次调用 shutdown(),其中一次以 SHUT_RD 为参数,另一次以 SHUT_WR 为参数。
close会关闭连接,并释放所有连接对应的资源,而shutdown并不会释放掉套接字和所有资源。
close有引用计数,例如父子进程都打开了某个文件描述符,其中某个进程调用了close函数,会使close函数的引用计数减1,直到套接字的引用计数为0,才会真正的关闭连接。而shutdown函数可以无视引用计数,直接关闭连接。
close的引用计数的存在导致不一定会发出FIN结束报文,而shutdown一定会发出FIN报文。
shutdown() 用来关闭连接,而不是套接字,不管调用多少次 shutdown(),套接字依然存在,直到调用 close() / closesocket() 将套接字从内存清除。
调用 close()关闭套接字时,或调用 shutdown() 关闭输出流时,都会向对方发送 FIN 包。FIN 包表示数据传输完毕,计算机收到 FIN 包就知道不会再有数据传送过来。
默认情况下,close()引用计数为0后会立即往网络中发送FIN包,不管输出缓冲区中是否还有数据,而shutdown() 会等输出缓冲区中的数据传输完毕再发送FIN包。也就意味着,调用 close()将丢失输出缓冲区中的数据,而调用 shutdown() 不会。
LINGER结构包含了指定套接字的信息,当调用了close()函数关闭该套接字时,LINGER结构中的信息指定了如何处理未发送的数据。
typedef struct linger {
u_short l_onoff;
u_short l_linger;
} LINGER, *PLINGER, *LPLINGER;
设置 l_onoff为0,则该选项关闭,l_linger的值被忽略,等于内核缺省情况,close调用会立即返回给调用者,如果可能将会传输任何未发送的数据;
设置 l_onoff为非0,l_linger为0,当调用close的时候,TCP连接会立即断开.send buffer中未被发送的数据将被丢弃,并向对方发送一个RST信息.值得注意的是,由于这种方式,不是以4次握手方式结束TCP链接,所以,TCP连接将不会进入TIME_WAIT状态,这样会导致新建立的可能和就连接的数据造成混乱。这种关闭方式称为“强制”或“失效”关闭。
设置 l_onoff 为非0,l_linger为非0,在这种情况下,会使得close返回得到延迟。调用close去关闭socket的时候,内核将会延迟。也就是说,如果send buffer中还有数据尚未发送,该进程将会被休眠直到一下任何一种情况发生:
a. send buffer中的所有数据都被发送并且得到对方TCP的应答消息(这种应答并不是意味着对方应用程序已经接收到数据)
b.延迟时间消耗完。在延迟时间被消耗完之后,send buffer中的所有数据都将会被丢弃。
这种关闭称为“优雅的”关闭。
setsockopt()函数的作用是设置套接字的选项。
使用示例:
struct linger tmp = {1, 1};
setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &tmp, sizeof(tmp));