网络编程 TCP/UDP

 

目录

1、参考文章:

2、TCP 编程流程:

2.1、端口状态:

3、UDP 编程流程:

3.1、端口状态:

4、WSASocket()和socket()函数区别:

5、SOCK_DGRAM和SOCK_STREAM:

6、send/sendto和recv/recvfrom区别:

7、select

8、epoll

8.1、理解

8.2、数据结构

9、TCP的阻塞机制


 

 

1、参考文章:

理论:基础参考文章

代码:参考代码

2、TCP 编程流程:

网络编程 TCP/UDP_第1张图片

2.1、端口状态:

listen():

accept():

connect():

可以看到7688进程有两个socket管道、一个是1002本地监听、另一个是与外界通信socket。

1、这里先关闭客户端socket:

因为是客户端主动触发的关闭,所以服务端的状态变为CLOSE_WAIT状态,客户端状态变为FIN_WAIT_1->FIN_WAIT_2;这时不关闭服务端接受到的客户端的socket,等待2MSL后,就只剩服务端的监听端口了;

如果在2MSL之内,关闭服务端接受到的客户端的socket,则客户端socket进入TIME_WAIT状态:

2、反之先关闭服务端接收到的客户端socket,也是一样的效果;

3、这里直接先关闭服务端的监听socket:

所以任然可以用已建立连接的socket发消息;

3、UDP 编程流程:

网络编程 TCP/UDP_第2张图片

3.1、端口状态:

bind():

没有连接状态;

并且关闭客户端socket不会改变服务器进程的状态,只有当服务端的socket关闭时进程才会结束,并且是立即结束。

4、WSASocket()和socket()函数区别:

WSASocket是Windows专用,支持异步操作;socket是unix标准,只能同步操作。

Socket可采用多线程实现非阻塞,winsock是socket的windows平台的实现。winsock是微软专门为windows操作系统开发的socket网络编程接口,而socket是通用网络编程接口。

socket() 函数创建一个通讯端点并返回一个套接口。但是在socket库中例程在应用于阻塞套接口时会阻塞。WSASocket()的发送操作和接收操作都可以被重叠使用。接收函数可以被多次调用,发出接收缓冲区,准备接收到来的数据。发送函数也可以被多次调用,组成一个发送缓冲区队列。可是socket()却只能发过之后等待回消息才可做下一步操作!

5、SOCK_DGRAM和SOCK_STREAM:

SOCK_STREAM 是有保障的(即能保证数据正确传送到对方)面向连接的SOCKET,多用于资料(如文件)传送。

SOCK_DGRAM 是无保障的面向消息的socket , 主要用于在网络上发广播信息。

SOCK_STREAM 是基于TCP的,数据传输比较有保障。SOCK_DGRAM是基于UDP的,专门用于局域网,基于广播

SOCK_STREAM 是数据流,一般是tcp/ip协议的编程,SOCK_DGRAM分是数据抱,是udp协议网络编程

6、send/sendto和recv/recvfrom区别:

send(),recv()用于TCP,sendto()及recvfrom()用于UDP;

但是send(),recv()也可以用于UDP,sendto()及recvfrom()也可以用于TCP;

如果send(),recv()用于UDP,在send之前先要使用connect(客户端能发服务端能收、反之不行,why??,如果服务端使用recvfrom/sendto,客户端使用recv/send就可以正常收发);

int send(SOCKET s, const char FAR *buf, int len, int flags);
int recv(SOCKET s, char FAR * buf, int len, int flags);
int sendto(int sockfd, const void *msg, int len, unsigned int flags, const struct sockaddr *to, int tolen);
int recvfrom(int sockfd, void *buf ,int len ,unsigned int lags ,struct sockaddr *from ,int *fromlen);

recvfrom注意第六个参数,是指针,意思是这个参数是会被修改的;

关于flags参数:

int send(int s, const void *msg, size_t len, int flags); 
flags取值有:
0: 与write()无异
MSG_DONTROUTE:告诉内核,目标主机在本地网络,不用查路由表
MSG_DONTWAIT:将单个I/O操作设置为非阻塞模式
MSG_OOB:指明发送的是带外信息

int recv(int s, void *buf, size_t len, int flags);
flags取值有:
0:常规操作,与read()相同
MSG_DONTWAIT:将单个I/O操作设置为非阻塞模式
MSG_OOB:指明发送的是带外信息
MSG_PEEK:可以查看可读的信息,在接收数据后不会将这些数据丢失
MSG_WAITALL:通知内核直到读到请求的数据字节数时,才返回。

int recvfrom(int s, void *buf, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen); 
flags取值有:
0:常规操作,与read()相同
MSG_OOB:指明发送的是带外信息
MSG_PEEK:可以查看可读的信息,在接收数据后不会将这些数据丢失

int sendto(int s, const void *msg, size_t len, int flags, const struct sockaddr *to, socklen_t tolen); 
flags取值有:
0: 与write()无异
MSG_DONTROUTE:告诉内核,目标主机在本地网络,不用查路由表
MSG_OOB:指明发送的是带外信息

7、select

int select(int maxfdp, fd_set* readfds, fd_set* writefds, fd_set* errorfds, struct timeval* timeout);

maxfdp:需要检查的文件描述字个数(即检查到fd_set的第几位),数值应该比三组fd_set中所含的最大fd值更大,一般设为三组fd_set中所含的最大fd值加1(如在readset,writeset,exceptset中所含最大的fd为5,则nfds=6,因为fd是从0开始的)。设这个值是为提高效率,使函数不必检查fd_set的所有1024位;

readfds:用来检查可读性的一组文件描述字,(数组)可读句柄,先从该参数中读到可读句柄,然后复制给第一个元素(windows上测试结果);

writefds:同上,用来检查可写性的一组文件描述字;

errorfds:同上,用来检查是否有异常条件出现的文件描述字。(注:错误不包括在异常条件之内);

timeout:用于描述一段时间长度,如果在这个时间内,需要监视的描述符没有事件发生则函数返回,返回值为0。 
有三种可能:

  • timeout=NULL(阻塞:select将一直被阻塞,直到某个文件描述符上发生了事件)
  • timeout所指向的结构设为非零时间(等待固定时间:如果在指定的时间段里有事件发生或者时间耗尽,函数均返回)
  • timeout所指向的结构,时间设为0(非阻塞:仅检测描述符集合的状态,然后立即返回,并不等待外部事件的发生)

fd_set(它比较重要所以先介绍一下)是一组文件描述字(fd)的集合,它用一位来表示一个fd(下面会仔细介绍),对于fd_set类型通过下面四个宏来操作:

FD_ZERO(fd_set *fdset):将指定的文件描述符集清空,在对文件描述符集合进行设置前,必须对其进行初始化,如果不清空,由于在系统分配内存空间后,通常并不作清空处理,所以结果是不可知的。

FD_SET(fd_set *fdset):用于在文件描述符集合中增加一个新的文件描述符。 

FD_CLR(fd_set *fdset):用于在文件描述符集合中删除一个文件描述符。 (windows上测试的时候,并没有清除掉

FD_ISSET(int fd,fd_set *fdset):用于测试指定的文件描述符是否在该集合中。

8、epoll

https://www.cnblogs.com/aspirant/p/9166944.html

8.1、理解

假设你在大学中读书,要等待一个朋友来访,而这个朋友只知道你在A号楼,但是不知道你具体住在哪里,于是你们约好了在A号楼门口见面。如果你使用的阻塞IO模型来处理这个问题,那么你就只能一直守候在A号楼门口等待朋友的到来,在这段时间里你不能做别的事情,不难知道,这种方式的效率是低下的。

进一步解释select和epoll模型的差异:

select版大妈做的是如下的事情:比如同学甲的朋友来了,select版大妈比较笨,她带着朋友挨个房间进行查询谁是同学甲,你等的朋友来了。

int n = select(&readset,NULL,NULL,100);
 for (int i = 0; n > 0; ++i)
 {
        if (FD_ISSET(fdarray[i], &readset))
        { 
               do_something(fdarray[i]);
               --n; 
        } 
}

epoll版大妈就比较先进了,她记下了同学甲的信息,比如说他的房间号,那么等同学甲的朋友到来时,只需要告诉该朋友同学甲在哪个房间即可,不用自己亲自带着人满大楼的找人了。

n = epoll_wait(epfd,events,20,500);
 for(i=0;i

在epoll中,关键的数据结构epoll_event定义如下:

typedef union epoll_data 
{ 
     void *ptr; 
     int fd;
      __uint32_t u32;
      __uint64_t u64;
} epoll_data_t;

struct epoll_event
{ 
     __uint32_t events; /* Epoll events */
     epoll_data_t data; /* User data variable */ 
};

可以看到,epoll_data是一个union结构体,它就是epoll版大妈用于保存同学信息的结构体,它可以保存很多类型的信息:fd,指针,等等。

有了这个结构体,epoll大妈可以不用吹灰之力就可以定位到同学甲。别小看了这些效率的提高,在一个大规模并发的服务器中,轮询IO是最耗时间的操作之一。

再回到那个例子中,如果每到来一个朋友楼管大妈都要全楼的查询同学,那么处理的效率必然就低下了,过不久楼底就有不少的人了。

8.2、数据结构

首先epoll_create创建一个epoll文件描述符,底层同时创建一个红黑树,和一个就绪链表;红黑树存储所监控的文件描述符的节点数据,就绪链表存储就绪的文件描述符的节点数据;epoll_ctl将会添加新的描述符,首先判断是红黑树上是否有此文件描述符节点,如果有,则立即返回。如果没有, 则在树干上插入新的节点,并且告知内核注册回调函数。当接收到某个文件描述符过来数据时,那么内核将该节点插入到就绪链表里面。epoll_wait将会接收到消息,并且将数据拷贝到用户空间,清空链表。对于LT模式epoll_wait清空就绪链表之后会检查该文件描述符是哪一种模式,如果为LT模式,且必须该节点确实有事件未处理,那么就会把该节点重新放入到刚刚删除掉的且刚准备好的就绪链表,epoll_wait马上返回。ET模式不会检查,只会调用一次

此处重点谈一下ET和LT模式,ET模式是将就绪的事件传给用户的event结构体数组后,在将事件传给一个列表txlist,进而返回给就绪列表,下一次传递会再检测事件用户是否完全接收数据,将未完全接收就绪事件再次传递给用户,重复步骤,直至用户全部接受数据为止。

LT模式为将就绪事件传递给用户之后,不必返回给就绪列表,可能存在丢失数据的可能。

9、TCP的阻塞机制

https://blog.csdn.net/shuxnhs/article/details/80644531

我们知道TCP通过一个定时器(timer)采样了RTT并计算RTO,但是,如果网络上的延时突然增加,那么,TCP对这个事做出的应对只有重传数据,然而重传会导致网络的负担更重,于是会导致更大的延迟以及更多的丢包,这就导致了恶性循环,最终形成“网络风暴” —— TCP的拥塞控制机制就是用于应对这种情况。 

首先需要了解一个概念,为了在发送端调节所要发送的数据量,定义了一个“拥塞窗口”(Congestion Window),在发送数据时,将拥塞窗口的大小与接收端ack的窗口大小做比较,取较小者作为发送数据量的上限。 
拥塞控制主要是四个算法: 

1、慢启动:

意思是刚刚加入网络的连接,一点一点地提速,不要一上来就把路占满。 连接建好的开始先初始化cwnd = 1,表明可以传一个MSS大小的数据。 

每当收到一个ACK,cwnd++;呈线性上升,每当过了一个RTT,cwnd = cwnd*2;呈指数让升。阈值ssthresh(slow start threshold),是一个上限,当cwnd >= ssthresh时,就会进入“拥塞避免算法” 。

2、拥塞避免:

当拥塞窗口 cwnd 达到一个阈值时,窗口大小不再呈指数上升,而是以线性上升,避免增长过快导致网络拥塞。 每当收到一个ACK,cwnd = cwnd + 1/cwnd,每当过了一个RTT,cwnd = cwnd + 1,

拥塞发生:当发生丢包进行数据包重传时,表示网络已经拥塞。分两种情况进行处理: 等到RTO超时,重传数据包;sshthresh = cwnd /2;cwnd 重置为 1 
3、进入慢启动过程 

在收到3个duplicate ACK时就开启重传,而不用等到RTO超时,sshthresh = cwnd = cwnd /2,进入快速恢复算法——Fast Recovery 
4、快速恢复:

至少收到了3个Duplicated Acks,说明网络也不那么糟糕,可以快速恢复。 cwnd = sshthresh + 3 * MSS (3的意思是确认有3个数据包被收到了) 。重传Duplicated ACKs指定的数据包,如果再收到 duplicated Acks,那么cwnd = cwnd +1,如果收到了新的Ack,那么,cwnd = sshthresh ,然后就进入了拥塞避免的算法了。

你可能感兴趣的:(C/C++/C#,NetWork)