持续更新中…
服务器和客户端的一般流程
服务器端:
socket()-->bind( )-->listen()-->accept()-->read()/write()--->close()
socket()//创建套接字
bind()//分配套接字地址
listen()//等待连接请求状态
accept()//允许连接
read()/write()//进行数据交换
close()//断开连接
客户端:
socket()-->connect()-->read()/write()-->close()
socket()//创建套接字
connect()//请求连接
read()/write()//进行数据交换
close()//断开连接
补充知识
1)char**
最常使用在main函数中:
int main(int argc,char**argv);
等同于int main(int argc,char*argv[])
等同于int main(int argc,string argv)(C语言中并不直接包含字符串类型,此处便于理解,所以使用string类型)
临界段使用步骤
1、首先定义一个临界段对象(通常全局变量)
CRITICAL_SECTION cs
2、在main函数中临界段对象初始化
InitializeCriticalSection (&cs)
3、在要执行关键代码进入临界段
EnterCriticalSection (&cs)
4、在执行完关键代码离开临界段
LeaveCriticalSection (&cs)
5、释放临界段对象
DeleteCriticalSection (&cs)
事件同步模型
一、TCP
- server端:
#include "stdafx.h"
#include <stdio.h>
#include <winsock2.h>
#pragma comment(lib,"ws2_32.lib")
int main(int argc, char* argv[])
{
//初始化WSA
WORD sockVersion = MAKEWORD(2,2);
WSADATA wsaData;
if(WSAStartup(sockVersion, &wsaData)!=0)
{
return 0;
}
//创建套接字
SOCKET slisten = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(slisten == INVALID_SOCKET)
{
printf("socket error !");
return 0;
}
//绑定IP和端口
sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(8888);
sin.sin_addr.S_un.S_addr = INADDR_ANY;
//INADDR_ANY 的具体含义是,绑定到0.0.0.0。此时,对所有的地址都将是有效的,如果系统考虑冗余,采用多个网卡的话,那么使用此种bind,将在所有网卡上进行绑定。在这种情况下,你可以收到发送到所有有效地址上数据包。
if(bind(slisten, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR)
{
printf("bind error !");
}
//开始监听
if(listen(slisten, 5) == SOCKET_ERROR)
{
printf("listen error !");
return 0;
}
//循环接收数据
SOCKET sClient;
sockaddr_in remoteAddr;
int nAddrlen = sizeof(remoteAddr);
char revData[255];
while (true)
{
printf("等待连接...\n");
sClient = accept(slisten, (SOCKADDR *)&remoteAddr, &nAddrlen);
if(sClient == INVALID_SOCKET)
{
printf("accept error !");
continue;
}
printf("接受到一个连接:%s \r\n", inet_ntoa(remoteAddr.sin_addr));
//接收数据
int ret = recv(sClient, revData, 255, 0);
if(ret > 0)
{
revData[ret] = 0x00;
printf(revData);
}
//发送数据
char * sendData = "你好,TCP客户端!\n";
send(sClient, sendData, strlen(sendData), 0);
closesocket(sClient);
}
closesocket(slisten);
WSACleanup();
return 0;
}
- client端:
#include "stdafx.h"
#include <WINSOCK2.H>
#include <STDIO.H>
#pragma comment(lib,"ws2_32.lib")
int main(int argc, char* argv[])
{
WORD sockVersion = MAKEWORD(2,2);
WSADATA data;
if(WSAStartup(sockVersion, &data) != 0)
{
return 0;
}
SOCKET sclient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(sclient == INVALID_SOCKET)
{
printf("invalid socket !");
return 0;
}
sockaddr_in serAddr;
serAddr.sin_family = AF_INET;
serAddr.sin_port = htons(8888);
serAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
if (connect(sclient, (sockaddr *)&serAddr, sizeof(serAddr)) == SOCKET_ERROR)
{
printf("connect error !");
closesocket(sclient);
return 0;
}
char * sendData = "你好,TCP服务端,我是客户端!\n";
send(sclient, sendData, strlen(sendData), 0);
char recData[255];
int ret = recv(sclient, recData, 255, 0);
if(ret > 0)
{
recData[ret] = 0x00;
printf(recData);
}
closesocket(sclient);
WSACleanup();
return 0;
}
二、UDP
- server端:
#include "stdafx.h"
#include <stdio.h>
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")
int main(int argc, char* argv[])
{
WSADATA wsaData;
WORD sockVersion = MAKEWORD(2,2);
if(WSAStartup(sockVersion, &wsaData) != 0)
{
return 0;
}
SOCKET serSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if(serSocket == INVALID_SOCKET)
{
printf("socket error !");
return 0;
}
sockaddr_in serAddr;
serAddr.sin_family = AF_INET;
serAddr.sin_port = htons(8888);
serAddr.sin_addr.S_un.S_addr = INADDR_ANY;
if(bind(serSocket, (sockaddr *)&serAddr, sizeof(serAddr)) == SOCKET_ERROR)
{
printf("bind error !");
closesocket(serSocket);
return 0;
}
sockaddr_in remoteAddr;
int nAddrLen = sizeof(remoteAddr);
while (true)
{
char recvData[255];
int ret = recvfrom(serSocket, recvData, 255, 0, (sockaddr *)&remoteAddr, &nAddrLen);
if (ret > 0)
{
recvData[ret] = 0x00;
printf("接受到一个连接:%s \r\n", inet_ntoa(remoteAddr.sin_addr));
printf(recvData);
}
char * sendData = "一个来自服务端的UDP数据包\n";
sendto(serSocket, sendData, strlen(sendData), 0, (sockaddr *)&remoteAddr, nAddrLen);
}
closesocket(serSocket);
WSACleanup();
return 0;
}
- client端:
#include "stdafx.h"
#include <stdio.h>
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")
int main(int argc, char* argv[])
{
WORD socketVersion = MAKEWORD(2,2);
WSADATA wsaData;
if(WSAStartup(socketVersion, &wsaData) != 0)
{
return 0;
}
SOCKET sclient = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(8888);
sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
int len = sizeof(sin);
char * sendData = "来自客户端的数据包.\n";
sendto(sclient, sendData, strlen(sendData), 0, (sockaddr *)&sin, len);
char recvData[255];
int ret = recvfrom(sclient, recvData, 255, 0, (sockaddr *)&sin, &len);
if(ret > 0)
{
recvData[ret] = 0x00;
printf(recvData);
}
closesocket(sclient);
WSACleanup();
return 0;
}
1. socket
**函数功能:**主要用于实现同步传输套接字的创建,在应用于网络操作时会阻塞。 P64
#include<sys/types.h>
#include<sys/socket.h>
int socket(int domain, int type, int protocol);
定义:
SOCKET WSASocket (
int af,
int type,
int protocol);
参数 | 描述 |
---|---|
domain | IP 地址类型:PF_LOCAL,AF_INET(ipv4) 和 AF_INET6(ipv6) |
type | 设置套接字通信类型:SOCK_STREAM(流格式套接字/面向连接的套接字) 和 SOCK_DGRAM(数据报套接字/无连接的套接字) |
protocol | 传输协议: IPPROTO_TCP 和 IPPTOTO_UDP |
Windows 不把套接字作为普通文件对待,而是返回 SOCKET 类型的句柄
SOCKET sock = socket(AF_INET, SOCK_STREAM, 0); //创建TCP套接字
2. WSASocket
**函数功能:**WSASocket是Windows专用,支持异步操作
s = WSASocket (AF_INET, SOCK_RAW, IPPROTO_ICMP , NULL, 0, WSA_FLAG_OVERLAPPED);
定义:
SOCKET WSASocket (
int af,
int type,
int protocol,
LPWSAPROTOCOL_INFO lpProtocolInfo,
GROUP g,
DWORD dwFlags
);
参数 | 描述 |
---|---|
af | 地址族描述。目前仅支持PF_INET格式,亦即ARPA Internet地址格式。 |
type | 新套接口的类型描述。 |
protocol | 套接口使用的特定协议,如果调用者不愿指定协议则定为0。 |
lpProtocolInfo | 一个指向PROTOCOL_INFO结构的指针,该结构定义所创建套接口的特性。如果本参数非零,则前三个参数(af, type, protocol)被忽略。 |
g | 套接口组的描述字。 |
dwFlags | 套接口属性描述。 |
返回值:
若无错误发生,WSASocket()返回新套接口的描述字。否则的话,返回 INVALID_SOCKET,应用程序可以调用WSAGetLastError()来获取相应的错误代码。
3. bind
函数功能:bind
API能够将套接字文件描述符、端口号和ip
绑定到一起 P66
注意:
绑定的一定是自己的 ip和和端口,不是对方的;比如对于TCP服务器来说绑定的就是服务器自己的ip和端口
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数 | 描述 |
---|---|
sockfd | 表示socket 函数创建的通信文件描述符 |
addrlen | 表示所指定的结构体变量的大小 |
addr | 表示struct sockaddr 的地址,用于设定要绑定的ip和端 |
定义:
int bind(
__in SOCKETS s,
__in const struct sockaddr *addr,
__in int addrlen
);
bind()函数的返回值为0时表示绑定成功,-1表示绑定失败
参考:
4. listen
函数功能:将套接字文件描述符从主动转为被动文件描述符(只能被动得等待别人主动发送数据,再回应),然后用于被动监听客户端的连接 P68
#include <sys/socket.h>
int listen(int sockfd, int backlog);
参数 | 描述 |
---|---|
sockfd | 表示socket创建的套接字文件描述符 |
backlog | 指定队列的容量 这个队列用于记录正在连接但是还没有连接完成的客户端,一般设置队列的容量为2,3即可。队列的最大容量需要小于30 |
函数返回值:成功返回0,失败返回-1
5. connect
**函数功能:**向服务端发起连接请求 P67
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数 | 描述 |
---|---|
sockfd | socket文件描述符 |
addr | 传入参数,指定服务器端地址信息,含IP地址和端口号 |
addrlen | 传入参数,传入sizeof(addr)大小 |
在之前的bind()函数的博客中我们就详细的讨论了,客户端是可以使用 bind()的,但是没必要.
所以我们才可以说客户端编程是非常简单的事,因为你只需要知道服务器端的IP,端口号就行(当然协议是要一样的。)那么就能完成通信。
函数返回值:
成功: 0
失败:-1,设置errno
补充:
当客户端调用 connect()函数之后,发生一下情况之一才会返回(完成函数调用):
服务器端接收连接请求
发生断网的异常情况而终端连接请求
需要注意的是,所谓的“接收连接”并不意味着服务器调用 accept()函数,。其实是服务器端把连接请求信息记录到等待队列。因此 connect()函数返回后并不进行数据交换。而是要等服务器端 accept 之后才能进行数据交换(read、write)。
客户端端需要调用connect()连接服务器,connect和bind的参数形式一致,区别在于bind的参数是自己的地址,而connect的参数是对方的地址。
这就是客户端的难点所在了。
当使用UDP协议时,客户端调用connect()函数完全是在本地操作,不会产生任何网络数据。起到的作用是:为套接字关联远程主机的地址和端口号。在特定情况下可以提高传输速率,如UDP服务器与单个客户长时间的通信。同时连接模式保证应用程序接收到的数据只能是连接对等方发来的数据,不会受到其他应用程序发来的噪声数据的影响。
6. accept
函数功能:被动监听客户端发起的tcp连接请求,三次握手后连接建立成功。客户端connect
函数请求发起连接。 P69
连接成功后服务器的tcp协议会记录客端的ip和端口,如果是跨网通信,记录ip的就是客户端所在路由器的公网ip
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数 | 描述 |
---|---|
sockfd | sockfd 已经被listen转为了被动描述符的“套接字文件描述符”,专门用于客户端的监听(监听套接字),如果sockfs没有被listen函数转为被动描述符,则accept是无法将其用来监听客户端连接的。 套接字文件描述符默认是阻塞的,即如果没有客户端请求连接的时候,此时accept会阻塞,直到有客户端连接;如果不想套接字文件描述符阻塞,则可以创建套接字 socket函数 时指定type为SOCK_NOBLOCK |
addrlen | 表示第二个参数addr 的大小,不顾要求给定地址 |
addr | 用于记录发起连接请求的那个客户端的IP端口 |
建立连接时服务器的TCP协议会自动解析客户端发来的数据包,从中获取客户端的IP和端口号
这里如果服务器应用层需要用到客户端的 IP和端口号,可以给accept指定第二个参数addr
,以获取TCP链接时的客户端ip和端口号;如果服务器应用层不需要,则写NULL
即可
函数返回值:
成功:返回一个通信描述符,专门用于与连接成功的客户端进行通信。(已连接套接字)
失败:返回-1 ,并设置errno
7. memset
**功能:**将s所指向的某一块内存中的每个字节的内容全部设置为ch指定的ASCII值,块的大小由第三个参数指定,这个函数通常为新申请的内存做初始化工作。
#include <sys/socket.h>
void *memset(void *s, char ch, unsigned n);
8. recv
**函数功能:**接收对方发送的数据。可以同样使用recvfrom函数来接收数据 P70
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
当recvfrom函数的最后两个参数写为NULL和0的时候与recv的功能完全一样
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
参数 | 描述 |
---|---|
sockfd | 通信文件描述符(已连接套接字的描述符) |
buf | 应用缓存,用于存放要接收的数据。可以是任何类型:结构体,int , char,float,字符串 |
len | buf的大小 |
flags | flags 一般设置为0,此时send为阻塞式发送即发送不成功会一直阻塞,直到被某个信号终端终止,或者直到发送成功为止。 指定 MSG_NOSIGNAL ,表示当连接被关闭时不会产生SIGPIPE 信号指定 MSG_DONTWAIT 表示非阻塞发送指定 MSG_OOB 表示带外数据 |
返回值:成功返回发送的字节数;失败返回-1,同时errno被设置
9. send
**函数功能:**向对方发送数据。其实也可以使用sendto函数: P69
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
如果sendto后面的两个参数为NULL和0时与send函数的作用是等价得。sendto函数一般用作UDP通信
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
参数 | 描述 |
---|---|
sockfd | sockfd 用于通信的文件描述符服务器:sockfd为 accept 返回的通信描述符,即已连接套接字的描述符 |
buf | 应用缓存,用于存放要发送的数据 可以是任何类型:结构体,int , char,float,字符串 |
len | buf的大小 |
flags | 一般设置为0,此时send为阻塞式发送 即发送不成功会一直阻塞,直到被某个信号终端终止,或者直到发送成功为止。 指定 MSG_NOSIGNAL ,表示当连接被关闭时不会产生SIGPIPE 信号指定 MSG_DONTWAIT 表示非阻塞发送 |
10. closesocket
**功能:**告诉底层协议栈关闭通信,释放与套接字关联的任何资源。一旦调用了该函数,之后再进行套接字操作,会导致错误的产生。P65,P101
定义:
int closesocket (
__in SOCKET s
);
返回值:
非负值表示成功。-1表示失败。
11. getsockname
**功能:**获取与某个套接字关联的本地协议地址,用于在对本地套接字进行了bind()调用或connect()调用之后。 P67
定义:
int getsockname(int sockfd, struct sockaddr *localaddr, socklen_t *addrlen);
参数 | 描述 |
---|---|
sockfd | 表示socket 函数创建的通信文件描述符 |
addrlen | 表示所指定的结构体变量的大小 |
addr | 表示struct sockaddr 的地址,用于获取这个套接字的名字信息即这个套接字所绑定的本地主机地址和端口号 |
12. getpeername
**功能:**用于获得已连接套接字的对等方端点地址。
定义:
int getpeername(int sockfd, struct sockaddr *peeraddr, socklen_t *addrlen);
getsockname函数与getpeername函数的使用
13. getaddrinfo
**函数功能:**完成主机名到地址解析。能够处理名字到地址以及服务到端口这两种转换。
定义:
int getaddrinfo( const char *hostname, const char *service, const struct addrinfo *hints, struct addrinfo **result );
参数 | 描述 |
---|---|
hostname | 一个主机名或者地址串(IPv4的点分十进制串或者IPv6的16进制串) |
service | 服务名可以是十进制的端口号,也可以是已定义的服务名称,如ftp、http等 |
hints | 是一个空指针或指向一个addrinfo结构的指针,由调用者填写关于它所想返回的信息类型的线索 |
result | 存放返回addrinfo结构链表的指针,指向由一个或多个addrinfo结构体组成的链表,包含了主机的响应信息 返回值:成功返回0,失败返回非零的 sockets error code |
14. freeaddrinfo
15. gethostbyname
**函数功能:**域名到ip的转换
函数原型:
struct hostent *gethostbyname(const char *hostname);
如:gethostbyname("www.baidu.com"),这里之所以可以直接输入字符串是因为用”“括起来字符串代表的就是其字符串地址。我记得好像是这样的。
hostname 为主机名,也就是域名。使用该函数时,只要传递域名字符串,就会返回域名对应的 IP 地址。返回的地址信息会装入 hostent 结构体
16. addrinfo结构
struct addrinfo
{
int ai_flags; /* customize behavior */
int ai_family; /* address family */
int ai_socktype; /* socket type */
int ai_protocol; /* protocol */
socklen_t ai_addrlen; /* length in bytes of address */
struct sockaddr *ai_addr; /* address */
char *ai_canonname; /* canonical name of host */
struct addrinfo *ai_next; /* next in list */
参数 | 描述 |
---|---|
ai_flags | getaddrinfo函数的调用选项 |
ai_family | 地址族 |
ai_socktype | 套接字类型 |
ai_protocol | 协议 |
ai_addrlen | ai_addr指向的sockaddr结构的缓冲区字节长度 |
ai_addr | 以scokaddr结构描述的地址信息 |
ai_canonname | 主机的正规名称 |
ai_next | 指向下一个addrinfo结构 |
17. honstent结构
struct hostent{
char *h_name; //official name
char **h_aliases; //alias list
int h_addrtype; //host address type
int h_length; //address lenght
char **h_addr_list; //address list
}
- h_name:官方域名(Official domain name)。官方域名代表某一主页,但实际上一些著名公司的域名并未用官方域名注册。
- h_aliases:别名,可以通过多个域名访问同一主机。同一 IP 地址可以绑定多个域名,因此除了当前域名还可以指定其他域名。
- h_addrtype:gethostbyname() 不仅支持 IPv4,还支持 IPv6,可以通过此成员获取IP地址的地址族(地址类型)信息,IPv4 对应 AF_INET,IPv6 对应 AF_INET6。
- h_length:保存IP地址长度。IPv4 的长度为 4 个字节,IPv6 的长度为 16 个字节。
- h_addr_list:这是最重要的成员。通过该成员以整数形式保存域名对应的 IP 地址。对于用户较多的服务器,可能会分配多个 IP 地址给同一域名,利用多个服务器进行均衡负载。
hostent 结构体变量的组成如下图所示:
18. scokaddr结构
用于存储参与(IP)Windows套接字通信的计算机上的一个internet协议(IP)地址。为了统一地址结构的表示方法 ,统一接口函数,使得不同的地址结构可以被bind()、connect()、recvfrom()、sendto()等函数调用。但一般的编程中并不直接对此数据结构进行操作,而使用另一个与之等价的数据结构sockaddr_in。这是由于Microsoft TCP/IP套接字开发人员的工具箱仅支持internet地址字段,而实际填充字段的每一部分则遵循sockaddr_in数据结构,两者大小都是16字节,所以二者之间可以进行切换。
struct sockaddr
{
ushort sa_family; //所选协议族AF_INET
char sa_data[14]; //ip地址及端口号
}
参数 | 描述 |
---|---|
sa_family | 是2字节的地址家族,一般都是“AF_xxx”的形式,它的值包括三种:AF_INET,AF_INET6和AF_UNSPEC。 如果指定AF_INET,那么函数就不能返回任何IPV6相关的地址信息; 如果仅指定了AF_INET6,则就不能返回任何IPV4地址信息。 AF_UNSPEC则意味着函数返回的是适用于指定主机名和服务名且适合任何协议族的地址。如果某个主机既有AAAA记录(IPV6)地址,同时又有A记录(IPV4)地址,那么AAAA记录将作为sockaddr_in6结构返回,而A记录则作为sockaddr_in结构返回通常用的都是AF_INET。 |
sa_data | ip地址及端口号 |
网络编程——sockaddr 与 sockaddr_in - cs_wu - 博客园 (cnblogs.com)
19. sockaddr_in结构
struct sockaddr_in {
short int sin_family; /* Address family */
unsigned short int sin_port; /* Port number */
struct in_addr sin_addr; /* Internet address */
unsigned char sin_zero[8]; /* Same size as struct sockaddr */
};
参数 | 描述 |
---|---|
sin_family | 指代协议族,在socket编程中只能是AF_INET |
sin_port | 存储端口号(使用网络字节顺序) |
sin_addr | 存储IP地址,使用in_addr这个数据结构 |
sin_zero | 是为了让sockaddr与sockaddr_in两个数据结构保持大小相同而保留的空字节。 |
网络编程——sockaddr 与 sockaddr_in - cs_wu - 博客园 (cnblogs.com)
20. in_addr结构
typedef struct in_addr {
union {
struct{ unsigned char s_b1,s_b2, s_b3,s_b4;} S_un_b;
struct{ unsigned short s_w1, s_w2;} S_un_w;
unsigned long S_addr;
} S_un;
} IN_ADDR;
阐述下in_addr的含义,很显然它是一个存储ip地址的共用体有三种表达方式:
-
第一种用四个字节来表示IP地址的四个数字;
-
第二种用两个双字节来表示IP地址;
-
第三种用一个长整型来表示IP地址。
网络编程——sockaddr 与 sockaddr_in - cs_wu - 博客园 (cnblogs.com)
21. zeromemory
函数功能: ZeroMemory() 常被称为清零函数, 用0来填充一块内存区域。
**函数原型:**void ZeroMemory( PVOID Destination, SIZE_T Length );
参考 | 描述 |
---|---|
Destination | 指向一块准备用0来填充的内存区域的开始地址 |
Length | 准备用0来填充的内存区域的大小,按字节来计算 |
22. shutdown
**函数功能:**关闭连接 P104
函数原型:
int shutdown(int sock, int howto); //Linuxint shutdown(SOCKET s, int howto); //Windows
参数 | 描述 |
---|---|
sock | 需要断开的套接字 |
howto | howto 为断开方式 SD_RECEIVE:关闭接收操作,也就是断开输入流。 SD_SEND:关闭发送操作,也就是断开输出流。 SD_BOTH:同时关闭接收和发送操作。 |
shutdown() 用来关闭连接,而不是套接字,不管调用多少次 shutdown(),套接字依然存在,直到调用 close() / closesocket() 将套接字从内存清除。
返回值:
成功返回 0,失败返回-1
23. setsockpot
**函数功能:**用于任意类型、任意状态套接口的设置选项值。 P100
- 选项可能存在于多层协议中,它们总会出现在最上面的套接字层。
- 当操作套接字选项时,选项位于的层和选项的名称必须给出。
- 为了操作套接字层的选项,应该将层的值指定为SOL_SOCKET。
- 为了操作其它层的选项,控制选项的合适协议号必须给出。
函数原型:
int setsockopt(int sockFd, int level, int optname, const void *optval, socklen_t optlen);
参数 | 描述 |
---|---|
sockfd | 将要被设置或者获取选项的套接字 |
level | 选项定义的层次;支持SOL_SOCKET 、IPPROTO_TCP 、IPPROTO_IP 和IPPROTO_IPV6 。一般设成SOL_SOCKET 以存取socket层 SOL_SOCKET:通用套接字选项. IPPROTO_IP: IP选项. IPv4 套接口 IPPROTO_TCP: TCP选项. IPPROTO_IPV6: IPv6 套接口 |
optname | 欲设置的选项,有下列几种数值:![]() |
optval | 对于setsockopt(),指针,指向存放选项待设置的新值的缓冲区。获得或者是设置套接字选项.根据选项名称的数据类型进行转换。 |
optlen | optval缓冲区长度 |
返回值:
成功则返回0, 若有错误则返回-1, 错误原因存于errno.
24. linger结构
typedef struct linger {
u_short l_onoff;
u_short l_linger;
} LINGER, *PLINGER, *LPLINGER;
主要分四种情况: P101
l_onoff为0。则马上关闭socket(graceful),closesocket马上返回。并尽量在后台将内核发送缓冲区的数据发出去。这种情况正常四次挥手。
l_onoff非0,l_linger为0。closesocket马上返回(abortive),连接重置,发送RST到对端,并且丢弃内核发送缓冲区中的数据。这种情况非正常四次挥手,不会time_wait。
l_onoff非0,l_linger非0。这种情况又分为阻塞和非阻塞。
对于阻塞socket,则延迟l_linger秒关闭socket,直到发完数据或超时。超时则连接重置,发送RST到对端(abortive),发完则是正常关闭(graceful)。
对于非阻塞socket,如果closesocket不能立即完成,则马上返回错误WSAEWOULDBLOCK。
一般我们都是用情况1,跟不设置LINGER一样。这种情况在服务端的缺点是可能有大量处于time_wait的socket,占用服务器资源。
而对于非法连接,或者客户端已经主动关闭连接,或者服务端想要重启,我们可以使用情况2,强制关闭连接,这样可以避免time_wait。
socket选项LINGER介绍
25. WSAsend
**函数功能:**在一个已连接的套接口上发送数据,可以一次将不连续的缓冲区中的数据直接发送出去。 P108
函数原型:
int WSAAPI WSASend (
SOCKET s,
LPWSABUF lpBuffers,
DWORD dwBufferCount,
LPDWORD lpNumberOfBytesSent,
int iFlags,
LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);
参数 | 描述 |
---|---|
s | 标识一个已连接套接口的描述字。 |
lpBuffers | 一个指向WSABUF结构数组的指针。每个WSABUF结构包含缓冲区的指针和缓冲区的大小。 |
dwBufferCount | lpBuffers数组中WSABUF结构的数目。 |
lpNumberOfBytesSent | 如果发送操作立即完成,则为一个指向所发送数据字节数的指针。 |
iFlags | 标志位。 |
lpOverlapped | 指向WSAOVERLAPPED结构的指针(对于非重叠套接口则忽略)。 |
lpCompletionRoutine | 一个指向发送操作完成后调用的完成例程的指针。(对于非重叠套接口则忽略)。 |
返回值:
若无错误发生且发送操作立即完成,则WSASend()函数返回0。这时,完成例程(Completion Routine)应该已经被调度,一旦调用线程处于alertable状态时就会调用它。否则,返回SOCKET_ERROR 。通过 WSAGetLastError获得详细的错误代码。
26. WSAbuf结构
typedef struct __WSABUF
{
u_long len;
char FAR *buf;
} WSABUF, *LPWSABUF;
用于函数WSARecv() 和WSASend() 函数的第二个参数,同时第三个参数指定该参数做为数组时的数组元素个数。
而该结构在两个函数中的意义也有小小的不同,主要在于 WSABUF::len 这个参数。它做WSARecv() 函数的参数时,len 被理解为缓冲区WSABUF::buf 的长度。而做WSASend() 函数的参数时,它被解释为发送的总长度,从WSABUF::buf 开始。
27. sendto
函数功能:将数据由指定的socket 传给对方主机(UDP) p119
函数原型:
int sendto (SOCKET s, const char* buf, int len, int flags, const struct sockaddr* to, int tolen)
参数 | 描述 |
---|---|
s | 数据报套接字的描述符 |
buf | 指向要发送的字节序列的应用程序缓冲区 |
len | 发送缓冲区的字节长度 |
flags | 提供了一种改变套接字调用默认行为的方式,把flags设置为0用于指定默认的行为,另外数据传输还可以采用MSG_DONTROUTE(不经过本地的路由机制)和MSG_OOB(带外数据)两种方式进行 |
to | 被声明为一个指向sockaddr结构的指针,对于TCP/IP应用程序,该指针通常先被转换为以sockaddr_in或 sockaddr_in6结构保存的目的IP地址和端口号,然后在调用时进行指针的强制类型转换 |
tolen | 指定地址结构的长度,通常为sizeof(struct sockaddr_in)或 sizeof(struct sockaddr_in6) |
返回值:
成功返回发送数据的字节数;否则返回SOCKET_ERROR
28. recvfrom
函数功能:接收数据(UDP) P120
函数原型:
int recvfrom (SOCKET s, char* buf, int len, int flags, struct sockaddr* from, int fromlen)
参数 | 描述 |
---|---|
s | 数据报套接字的描述符 |
buf | 指向要保存接收数据的应用程序缓冲区 |
len | 接收缓冲区的字节长度 |
flags | 提供了一种改变套接字调用默认行为的方式,把flags设置为0用于指定默认的行为,另外数据传输还可以采用MSG_DONTROUTE(不经过本地的路由机制)和MSG_OOB(带外数据)两种方式进行 |
from | 被声明为一个指向sockaddr结构的指针,当recvfrom()函数成功返回后,将本次接收到的数据报的来源地址写入from指针指向的结构中 |
fromlen | 指明地址结构的长度 |
返回值:
成功返回实际接收的字节总数;否则返回SOCKET_ERROR.
29. WSAIoctl
函数功能:I/O控制命令用来控制套接字上I/O的行为,也可以用来获取套接字上未决的I/O信息 P54
函数原型:
int WSAIoctl(SOCKET s,DWORD dwIoControlCode,LPVOID lpvInBuffer,DWORD cbInBuffer,
LPVOID lpvOutBuffer,DWORD cbOutBuffer,LPDWORD lpcbBytesReturned,
LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine)
参数 | 描述 |
---|---|
s | 套接字句柄 |
dwIoControlCode | 描述将进行的操作的控制代码,在设置接收全部选项时使用SIO_RCVALL; |
lpvInBuffer | 指向输入缓冲区的地址 |
cbInBuffer | 描述输入缓冲区的大小 |
lpvOutBuffer | 指向输出缓冲区的地址 |
cbOutBuffer | 描述输出缓冲区的大小 |
lpcbBytesReturned | 输出参数,返回输出实际字节数的地址 |
lpOverlapped | 指向WSAOVERLAPPED结构的地址 |
lpCompletionRoutine | 指向操作结束后调用的例程指针 |
最后两个参数在使用重叠I/O时才有用。
30. ioctlsocket
作用:将套接字设置为非阻塞模式。 p162
函数原型:
int ioctlsocket( int s, long cmd, u_long * argp);
参数 | 描述 |
---|---|
s | 一个标识套接口的描述字 |
cmd | 对套接口s的操作命令 FIONBIO: 允许或禁止套接口s的非阻塞模式。argp指向一个无符号长整型,如允许非阻塞模式则非零,如禁止非阻塞模式则为零。 |
argp | 指针变量,指明cmd命令的参数 |
补:
FIONREAD:
确定套接口s自动读入的数据量。argp指向一个无符号长整型,其中存有ioctlsocket()的返回值。如果s是SOCKET_STREAM类型,则FIONREAD返回在一次recv()中所接收的所有数据量。这通常与套接口中排队的数据总量相同。如果S是SOCK_DGRAM 型,则FIONREAD返回套接口上排队的第一个数据报大小。
31. fd_set结构
函数功能:用于管理多个套接字 P167
函数原型:
typedefstruct fd_set {
u_int fd_count;
socket fd_array[FD_SETSIZE];
} fd_set;
参数 | 描述 |
---|---|
fd_count | 表示套接字数目 |
fd_array[FD_SETSIZE] | fd_array:存放套接字的数组 FD_SETSIZE:数组大小,默认为64 |
32. timeval
struct timeval
{
int tv_sec ; /* 秒数 */
int tv_usec ; /* 微秒 */
} ;
33. select
函数功能:监视socket集合,如果某个socket发生响应(链接或者收发数据),通过返回值以及参数告诉我们哪个socket有响应
函数原型: P167
int WSAAPI select(
int nfds, /*填0*/
fd_set *readfds, /*检查是否有可读的socket*/
fd_set *writefds, /*检查是否有可写的socket*/
fd_set *exceptfds, /*检查socket上的异常错误*/
const timeval *timeout
);
参数 | 描述 |
---|---|
nfds | 是为兼容之用,执行时被忽略。 |
readfds | readfds 中的fd 集合将由select 来监视是否可以读取 |
writefds | writefds 中的fds 集合将由select 来监视是否可以写入 |
exceptfds | 指向一个套接字集合,用来 检查错误 |
timeout | 是用来设置等待超时的 |
返回值:如果成功,则返回就绪的文件描述符的数量。超时一般返回0。错误返回-1。
注:
Readfds返回条件
1 连接请求
2 套接字输入缓冲区有数据可读
3 连接被关闭、重置或终止
Writefds返回条件
1 调用非阻塞connect,且连接成功
2 有数据可以发送
Exceptfds:
1调用非阻塞connect,连接失败
2 有外带数据可读
34. 处理fd_set的宏
FD_ZERO(fd_set *set)将一个文件描述符集合清零
FD_SET(int fd, fd_set *set)将文件描述符fd 加入集合set 中 – 这样select就会去监听它的状态。
FD_CLR(int fd, fd_set *set)将文件描述符fd 从集合set 中删除 – select不再去监听它的状态。
FD_ISSET(int fd, fd_set *set)检测fd在fdset集合中的状态是否变化。当检测到fd状态发送变化时返回真,否则,返回假。
35. CreateThread
函数功能:创建线程
函数原型:
HANDLE CreateThread (
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId );
如:
#define _WINSOCK_DEPRECATED_NO_WARNINGS 1
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include <winsock2.h>
#pragma comment (lib, "ws2_32.lib") //加载 ws2_32.dll
DWORD WINAPI ThreadFunc(PVOID pvParam)
{
printf("Created thread says ‘hello World’");
return 0;
}
int main()
{
HANDLE ThreadHandle = CreateThread(NULL, 0, ThreadFunc, NULL, 0, NULL);
Sleep(100);
printf("Main thread says ‘Hello World");
getchar();
return 0;
}
参数 | 描述 |
---|---|
lpThreadAttributes | 指定的是创建新进程内核对象的安全属性,不是线程访问字符串(Token)的属性,填0就可以。 |
dwStackSize | 用于指定线程初始时的栈大小,通常传入0即可,此时系统会使用一个合适的大小 |
lpStartAddress | 新进程入口函数的地址 |
lpParameter | 传入线程入口的参数,这个参数完全由调用者使用,系统只是简单的将这个参数传递给线程函数,并不做别的任何处理 |
dwCreationFlags | 指出创建线程的方式。填0就可以。表示线程一被创建就被立即执行 |
lpThreadId | 用于得到线程唯一的标识符。填0 |
返回值:线程创建成功,返回线程的句柄,失败,返回值是-1
36.事件同步相关函数
1. CreateEvent
函数功能:创建事件
函数原型:
HANDLE WINAPI CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes,
BOOL bManualReset,
BOOL bInitialState,
LPCTSTR lpName );
参数 | 描述 |
---|---|
lpEventAttributes | NULL |
bManualReset | 告诉系统是创建一个人工重置的事件(TRUE)还是创建一个自动重置的事件(FALSE)。常设置TRUE; |
bInitialState | 用于指明该事件是要初始化为已通知状态(TRUE)还是未通知状态(FALSE)。常设置为FALSE; |
lpName | 事件对象的名字,是一个字符串,名字对大小写敏感。 |
2. SetEvent
函数功能:将事件改为已通知状态
函数原型:
BOOL WINAPI SetEvent( HANDLE hEvent );
3. ResetEvent
函数功能:将函数改为未通知状态
函数原型:
BOOL WINAPI ResetEvent( HANDLE hEvent );
4. WaitForSingleObject
函数功能:等待单个事件变为通知态
函数原型:
DWORD WaitForSingleObject(HANDLE hObject,DWORD dwMilliseconds);
参数 | 描述 |
---|---|
hObject | 事件的句柄 |
dwMilliseconds | 指定要等待的毫秒数。如设为零,表示立即返回。如指定常数INFINITE,则可根据实际情况无限等待下去 |
5. WaitForMultipleObjects
函数功能:等待一组事件变为通知态
函数原型:
DWORD WaitForMultipleObjects(
DWORD nCount,
const HANDLE *lpHandles,
BOOL bWaitAll
DWORD dwMilliseconds);
参数 | 描述 |
---|---|
nCount | 监控对象的个数 |
lpHandles | 一组对象句柄的指针 |
bWaitAll | 是否等待全部符合条件,如果为TRUE,表示除非事件都变成通知态,否则就一直等待下去;如果FALSE,表示只要有一个事件都变成通知态,就立即返回 |
dwMilliseconds | 指定要等待的毫秒数。如设为零,表示立即返回。如指定常数INFINITE,则可根据实际情况无限等待下去 |
37. Windows的事件机制与使用
1. 创建对象 WSACreateEvent
WSAEVENT WSACreateEvent(void);
如果该函数调用成功,则函数返回值为事件对象句柄,
否则返回值为WSA_INVALID_EVENT。事件对象句柄实际上是DWORD类型。
2. 重置事件对象
BOOL WSAResetEvent(WSAEVENT hEvent);
实现将事件对象从“已授信”状态更改为“未授信”状态功能。
该函数参数为事件对象句柄。如果该函数调用成功,则函数返回TRUE;反之函数返回FALSE。
3. 设置事件对象为已授信状态
BOOL WSASetEvent(WSAEVENT hEvent);
实现将事件对象设置为“已授信”状态该函数参数为事件对象句柄。如果该函数调用成功,则函数返回TRUE;反之函数返回FALSE。
该函数参数为事件对象句柄。如果该函数调用成功,则函数返回TRUE;反之函数返回FALSE。
4. 关闭事件
BOOL WSACloseEvent(WSAEVENT hEvent);
函数参数为事件句柄。如果函数调用成功,该函数返回TRUE;否则返回FALSE。
38. 网络事件注册函数:WSAEventSelect
函数作用:为套接字注册感兴趣的网络事件,并将指定的事件对象关联到指定的网络事件集合。 P193
当应用程序调用该函数后,套接字被自动设置为非阻塞的工作模式
如果应用程序要将套接字设置为阻塞模式,必须设置InetworkEvents参数为0,再次调用WSAEventSelect()函数。
当应用程序在套接字上调用了WSAEventSelect()函数后,如果应用程序调用ioctlsocket()函数,试图将套接字设置为阻塞工作模式,将会失败会并返回WSAEINAL错误。
函数原型:
int WSAEventSelect(SOCKET s, WSAEVENT hEventObject, long lNetworkEvents)
SOCKET s;
WSAEVENT hEvent;
int nReVal=WSAEventSelect(s,hEvent,
FD_READ|FD_WRITE|FD_CLOSE);
参数 | 描述 |
---|---|
s | 套接字 |
hEventObject | 与网络事件集合相关联的事件对象句柄 |
lNetworkEvents | 感兴趣网络事件集合 |
返回值:成功返回0;否则返回SOCKET_ERROR
其中FD_WIRTE在FD_ACCEPT(accept),FD_CONNECT,缓冲区满了又变空之后事件发生后调用
39. 事件等待函数 WSAWaitForMultipleEvents
函数功能:等待网络事件发生。 P193
函数原型:
DWORD WSAWaitForMultipleEvents(
DWORD cEvents,
const WSAEVENT FAR *lphEvents,
BOOL fWaitAll,
DWORD dwTimeout,
BOOL fAlertable
);
参数 | 描述 |
---|---|
cEvents | 等待事件对象句柄数量至少为1,最多数量为WSA_MAXIMUM_WAIT_EVENTS,其值为64个 |
lphEvents | 指向事件对象句柄的指针。该参数和cEvents参数定义了一个由WSAEVENT数据类型元素构成的数组。通过该指针实现引用数组成员 |
fWaitAll | 指定等待的类型。 如果该参数为TRUE,则该函数在所有的事件对象都转变为“已授信”状态时才返回; 如果该参数为FALSE,则该函数在其中一个事件句柄转变为“已授信”状态时就返回,并且返回值指示出促使函数返回的事件对象。 |
dwTimeout | 调用该函数的阻塞时间,单位为毫秒。 超过等待的时间,即使没有满足fWaitAll参数指定的条件,该函数也会返回。 如果该参数为0,则该函数检查事件对象的状态并立即返回。 如果该参数为WSA_INFINITE,则该函数会无限期等待下去,直到满足fWaitALL参数指定的条件。 如果函数调用超时,则该函数返回值WSA_WAIT_TIMEOUT。 |
fAlertable | 该参数说明当完成例程在系统队列中排队等待执行时,该函数是否返回。如果该参数为TRUE,说明该函数返回时完成例程已经被执行。如果该参数为FALSE,说明该函数返回时完成例程还没有执行。该参数主要用于重叠I/O模型,在完成例程的处理过程使用。后面章节中还会对此详述,这里只需要将该参数设置为FALSE就可以了。 |
返回值:
WSAWaitForMultipEvents()函数返回时,返回值指出促使该函数返回的事件对象,其返回值分为下面3种情况。
(1)从WSA_WAIT_EVENT_0到WAS_WAIT_EVENT_0+cEvents-1范围的值:
如果fwaitALL参数为TRUE,则说明所有事件对象都处于“已传信”状态。
如果fwaitALL参数为FALSE,则返回值减去WAS_WAIT_EVENT_0,即为“已传信”事件对象在lphEvents数组中的序号。
WAS_WAIT_EVENT_0宏代表的值为0。
(2)WAIT_IO_COMPLETION:说明一个或者多个完成例程已经排队等执行。
(3)WSA_WAIT_TIMEOUT:说明函数调用超时,并且fwaitAll参数指定的事件对象没有处于“已授信”状态。
如果该函数调用失败,则返回值为WSA_WAIT_FAILED。
WSAWaitForMultipleEvents函数使用示例:
SOCKET socketArray[WSA_MAXIMUM_WAIT_EVENTS];
WSAEVENT eventArray[WSA_MAXIMUM_WAIT_EVENTS];
int nTotal=0; //数组长度
.......
DWORD dwIndex=WSAWaitForMultipleEvents(
nTotal,eventArray,FALSE,-1,FALSE);
WSAEVENT curEvent=eventArray[dwIndex-WSA_WAIT_EVENT_0];
SOCKETS curSocket=socketsArray[dwIndex-WSA_WAIT_EVENT_0];
40. 枚举网络事件函数 WSAEnumNetworkEvents
函数功能:获取给定套接字上发生的网络事件。
函数原型:
int WSAEnumNetworkEvents(
SOCKET s,
WSAEVENT hEventObject,
LPWSANETWORKEVENTS lpNetworkEvents);
参数 | 描述 |
---|---|
s | 发生网络事件的套接字句柄 |
hEventObject | 被重置的事件对象句柄(可选) |
lpNetworkEvents | 指向WSANETWORKEVENTS结构指针。在该结构中包含发生网络事件的记录和相关错误代码 |
返回值:
该函数调用成功时返回值为0,反之为SOCKETS_ERROR。
注:
如果hEventObject参数不为NULL,则该事件对象被重置。
如果该参数为NULL,需要调用WSAResetEvent()函数,设置事件对象为“未授信”状态。
如果该函数返回SOCKET_ERROR错误,则事件对象不会被重置,网络事件也不会被清除
41. WSANETWORKEVENTS
函数功能:记录了一个网络事件和相应的错误代码
函数原型:
typedef struct _WSANETWORKEVENTS {
long lNetworkEvents;
int iErrorCode[FD_MAX_EVENTS];
} WSANETWORKEVENTS, *LPWSANETWORKEVENTS;
参数 | 描述 |
---|---|
lNetworkEvents | 指示发生的网络事件。一个事件对象进入“已传信”状态时,在套接字上可能会发生多个网络事件。 |
iErrorCode[FD_MAX_EVENTS] | 包含网络事件错误代码的数组。错误代码与lnetworkEvents字段中的网络事件对应。 |
返回值:成功返回0;否则返回SOCKET_ERROR.
42 gethostname
函数功能:返回当前机器的标准主机名。namelen参数应指定name参数所指向的数组的大小。返回的名称应以null结尾,但如果namelen的长度不足以容纳主机名,则返回的名称应被截断,并且未指定返回的名称是否以null结尾。
函数原型:
// gethostname()
#include <unistd.h>
int gethostname(char *name, size_t namelen);
返回值:
成功时返回0;否则返回-1。
如果觉得帮助到你的话,希望能点个赞