您的位置:首页 > 理论基础 > 计算机网络

TCP/IP(7)——网络编程(2)多线程与并发式网络编程+RAW套接口+广播报文收发

2019-05-11 08:49 274 查看

我之前写过TCP/IP编程——多线程+非阻塞的服务实现,这篇文章是实现之前需要掌握的知识。

目录

多线程

多线程同步:锁

非阻塞

图形化代码流程:

UDP

TCP与UDP的区别

五元组标识TCP连接

五元组标识UDP连接

Raw Socket接口

广播报文收发

多播报文设计

本地接口加入多播组

我们网络编程(1)里介绍了单线程的TCP协议。

但是一般生活中,我们的服务都是多线程,并发的,并且是非阻塞的,有利于效率。

在win32库中提供了创建多线程的函数_beginthread与I/O复用函数(实现非阻塞)select

多线程

1.定义多线程的线程函数void workthread(LPVOID lpParam) 【返回值必须是void,传递参数可以用32bit的输入参数,即LPVPOD,LPVOID是一个没有类型的指针】

2.开启线程HANDLE hThread = (HANDLE) _beginthread(workthread, 0, hIOCP)【如果线程创建成功,函数会返回新线程的句柄】

3._endthread函数可以关闭线程。当线程执行完毕也会自动调 用该函数,该函数将释放线程中的所有资源。

多线程同步:锁

win32应用的事件对象机制可以用来通知等待线程某个条件已经满足。

如WriteThread在写完了缓存区的数据后,调用WSASetEvent(evToRead)便可以通知其他在等待的线程执行

没执行WSASetEvent(evToRead)之前,ReadThread通过WSAWaitForMultipleEvents (*,&evToRead,*,*,*)挂起等待事件对象被通知。

当然这两个函数只是win32提供的基础线程同步,还有DWORD WSAAPI WSAWaitForMultipleEvents等函数

非阻塞

调用select可以实现非阻塞。传统recv调用后会阻塞执行直到有数据可读。而select可以同时判断一个套接口(或者套接口集合,fd_set)的多种I/O状态(read、write、exception异常)。每次select返回都至少有一个套接口满足了I/O要求。

函数定义: int select( int nfds, fd_set FAR *readfds, fd_set FAR *writefds fd_set FAR *exceptfds, const struct timeval FAR *timeout);

参数含义: nfds 【 IN 】 : 同时select的套接口个数,已被忽略。

readfds 、 writefds 、 exceptfds 【 INOUT 】 : 描述字集合fd_set指针。

timeout【IN】:以TIMEVAL的形式告诉select函数需要等待的时间

图形化代码流程:

select的基本操作:FD_ZERO-->FD_SET-->SELECT-->FD_ISSET -->I/O操作 

  1. FD_ZERO后fdset的参数fdcount=0:没有fd在fdset中
  2. FD_SET后fdset中的fdcount是加入的fd的数量,然后调用select
  3. 事件发生后select返回的是一个fdset,这个set中的个数即满足条件的socket的个数
  4. 调用FD_ISSET查看fd是不是在返回的set中

2之后:                   3返回的结果

 

用例1:接受请求的非阻塞实现

[code]//阻塞实现
listen(socklisten, 5);
while(1) {
sAccept = accept(socklisten, struct sockaddr *)&cli, &ilen);
}
//非阻塞实现
listen(socklisten, 5);
SOCKET socka;//socka是用来接受请求的socket,和原来完成三次握手的不一样。
fd_set rfd; //readfds结构体
struct timeval timeout;//设置等待时间的结构体
timeout.tv_sec=60; //最多等待60s
timeout.tv_usec=0;
while (1) {
FD_ZERO(&rfd);//清空rfd
FD_SET(socklisten, &rfd);//将监听原来的socketlisten加入到rfd中
ret = select(0, &rfd, 0, 0, &timeout);//检查套节字是否可读,
if(FD_ISSET(sock, &rfd)){//如果套接字句柄还在fd_set里,说明客户端已经有connect的请求发过来了,马上可以accept成功
socka=accept(socklisten,0,0); //
}
}

更多demo请见:TCP/IP编程——多线程+非阻塞的服务实现

UDP

上面介绍的都是TCP基于流式数据传输,下面我们介绍基于数据报数据传输的UDP

数据报特性:在网络中独立传输,每个报文自身都包含地址、端口 号信息;
与TCP程序最大的不同是:UDP程序在客户和服务器之间不需要先建立连接,通信的任何一方可以先发送数据,而另一方直接接收就可以了。
 

这是普通的UDP迭代式程序模型,没有使用connect绑定。

TCP与UDP的区别

connect函数在UDP中不再发起三次握手而是建立起一个对应关系

区别1:socket函数

于UDP协议,socket函数使用如下

  • SOCKET s = socket(AF_INET, SOCK_DGRAW, IPPROTO_UDP)
  • SOCKET s = socket(AF_INET, SOCK_DGRAW, 0)
  • SOCKET s = WSASocket(AF_INET, SOCK_DGRAW, IPPROTO_UDP, NULL, WSA_FLAG_OVERLAPPERD)

区别2:connect函数

Connect函数不再发起三次握手,而是底层建立一个连接对应关系,发送时无需用sendto设置目标地址,直接用send即可,接收时只接收对应源地址的报文,即通过 recv即可接收报文。

区别3:sendto/recvfrom函数

如果不connect需要用sendto/recvfrom函数替换send/recv

 

五元组标识TCP连接

五元组标识UDP连接

 

Raw Socket接口

在程序中使用原始套接口(Raw Socket),就可以绕过需要TCP/IP 的传输层协议,直接对TCP/IP的底层进行操作,如对ICMP和IP进行实际 操作。 

使用原始套接口的典型代码如下

[code]SOCKET s;
s = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)

此处不细说,感兴趣请自行百度RawSocket的ping实现。

广播报文收发

只有UDP套接口才能实现广播和多播,但是默认不广播,需要配置。

使用setsockopt和getsockopt函数可以设置或读取套接口的选项值

[code]//判断socket是否支持广播
Bool bBroadcast;
int optlen = sizeof(bBroadcast);
if(getsockopt(sock, SOLSOCKET, SO_BROADCAST, (char *)&bBroadcast, &optlen) == SOCKET_ERROR){ //使用getsockopt读取套接口的选项值
HandleError(“getsockopt”);
closesocket(sock);
WSACleanup();
return -1;
}
if (bBroadcast)
printf(“Broadcast enabled default!\n”);
else
printf(“Broadcast disabled default!\n”);
————————————————————————————————————————————————————————————
//设置socket支持广播
Bool bBroadcast = true;
optlen = sizeof(bBroadcast);
if(setsockopt(sock, SOLSOCKET, SO_BROADCAST, (char *)&bBroadcast, &optlen) == SOCKET_ERROR) {
HandleError(“setsockopt”);
closesocket(sock);
WSACleanup();
return -1;
}

 

多播报文设计

广播一开始是为了用于资源发现和减少数据交互量。但是无论有没有参与广播应用,同一网段都需要对广播数据报进行处理,所以广播都会传到系统的协议栈中的监听端口的应用或者丢弃。

那为了解决这种无用功耗,多播产生了,只有加入多播地址的用户才会处理多播报文。

本地接口加入多播组

[code]Struct ip_mreq mreq;
Memcpy(&(mreq.imr_interface), local_if, sizeof(struct in_addr));
Memcpy(&(mreq.imr_multiaddr), mcaddr, sizeof(struct in_addr));
Setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&mreq, sizeof(mreq));//IP_ADD_MEMBERSHIP告诉本机的IP层和数据链路层接收发送到这个组的多播数据

退出多播组

[code]Struct ip_mreq mreq; Memcpy(&(mreq.imr_interface), local_if, sizeof(struct in_addr));
Memcpy(&(mreq.imr_multiaddr), mcaddr, sizeof(struct in_addr));
Setsockopt(s, IPPROTO_IP, IP_DROP_MEMBERSHIP, (char *)&mreq, sizeof(mreq));//IP_DROP_MEMBERSHIP

 

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: