文章目录
- 1. socket 函数:创建一个套接字
- socket的使用
- 2. bind函数:服务端对socket进行地址和端口的绑定
- 网络字节序相关函数
- bind的使用
- 3. listen函数:服务端监听socket套接字
- listen的使用
- 4. accept函数:服务端接受客户端连接请求
- accept的使用
- connect的使用
- 5. send函数:用send函数从TCP连接的另一端发送数据
- send的使用
- 6. recv函数:用recv函数从TCP连接的另一端接收数据
- recv的使用
- 7. sendto函数:从UDP连接的另一端发送数据
- sendto的使用
- 8. recvfrom函数:都用recv函数从UDP连接的另一端接收数据
- recvfrom的使用
- 9. shutdown函数:禁止在指定的套接字上发送和接收数据.并不会关闭套接字,在调用closesocket()函数之前,所有与该套接字相关的资源都不会被释放。
- 10. closesocket函数:关闭一个套接字
- 11. setsockopt函数:设置套接口的选项
- 12. getsockopt函数:获取套接口的选项的值
1. socket 函数:创建一个套接字
SOCKET socket(int af, int type, int protocol);
参数:
- 第一个参数指定应用程序使用的通信协议的协议族,对于TCP/IP协议族,该参数置AF INET;
- 第二个参数指定要创建的套接字类型,流套接字类型为SOCK_STREAM、数据报套接字类型为SOCK DGRAM;原始套接字SOCK_RAW
- 第三个参数指套接字使用的协议,IPPROTO_TCP,IPPROTOUDP,IPPROTO RAW,IPPROTO IP.
返回值:
该函数如果调用成功就返回新创建的套接字的描述符,失败返回INVALID SOCKET。套接字描述符是一个整数类型的值。
socket的使用
SOCKET s = socket(AF_INET, SOCK_STRAM, 0);
if(s == INVALID_SOCKET)
{
cout << "socket() Error:" << WSAGetLastError() << endl;
return 0;
}
2. bind函数:服务端对socket进行地址和端口的绑定
int bind(int sockfd, const struct sockaddr* addr, socklen_t addrlen);
参数:
- sockfd:即socket描述字,它socket()函数创建的唯一标识。
- addr:一个const struct sockaddr*指针,指向要绑定给sockfd的协议地址。
- addrlen:对应的结构的长度。
返回值:
- 成功返回0,错误返回SOCKET ERROR。
struct sockaddr{
unsigned short sa_family; // 2 bytes address family,AF_xxx
char sa_data[14]; // 14 bytes of protocol address
};
// IPv4 AF_INET sockets:
struct sockaddr_in{
short sin_family; // 2 bytes AF_INET,表示该socket位于Internet域
unsigned short sin_port; // 2 bytes htos() (网络字节顺序)
struct in_addr sin_addr; // 4 bytes inet_addr() 网络字节顺序存储IP地址
char sin_zero[8]; // 8 bytes 填充作用
}
这两个结构体都是16个字节,而且都有family属性,不同的是:sockaddr用其余14个字节来表示sa data,而sockaddr_in把14个字节拆分成端口、ip地址。
sin_zero用来填充字节使sockaddr_in和sockaddr保持一样大小。sockaddr是给操作系统用的,sockaddr_in区分了地址和端口,使用更方便。
网络字节序相关函数
htons()
:将16位无符号整数从主机字节序转换成网络字节序;
htonl()
:将32位无符号整数从主机字节序转换成网络字节序;
ntohs()
:将16位无符号整数从网络字节序转换成主机字节序;
ntohl()
:将32位无符号整数从网络字节序转换成主机字节序;
inet_addr()
:若字符串有效则将字符串转换为32位二进制网络字节序的IPV4地址,否则为INADDR NONE
inet_ntoa()
:将一个十进制网络字节序转换为点分十进制IP格式的字符串。
bind的使用
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
// INADDR_ANY就是指定地址为0.0.0.0的地址,这个地址事实上表示不确定的地址,或“所有地址”、“任意地址”。
addr.sin_port = htons(8000);
if(bind(s,(SOCKADDR*)&addr, sizeof(SOCKADDR)) == SOCKET_ERROR){
cout << "bind() Error:" << WSAGetLastError() << endl;
return 0;
}
3. listen函数:服务端监听socket套接字
int listen(int sockfd, int backlog);
参数:
- 第一个参数即为要监听的socket描述字
- 第二个参数为未完成队列的大小:正在等待完成相应的TCP三路握手过程的队列。这些套接口处于SYN_RCVD
返回值:
- 该函数如果调用成功就返回0,否则返回SOCKET_ERROR。可以调用WSAGetLastError()函数获取错误代码
listen的使用
if(listen(s, 30) == SOCKET_ERROR){
cout << "listen Error:" << WSAGetLastError() << endl;
return 0;
}
netstat -ao
查看监听状态
4. accept函数:服务端接受客户端连接请求
int accept(int sockfd, struct sockaddr *addr, socklen_t* addrlen);
参数:
- 第一个参数即为要监听的socket描述字
- 第二个参数为指向struct sockaddr*的指针,用于返回客户端的协议地址
- 第三个参数为协议地址的长度。
返回值:
- 成功返回由内核自动生成的一个全新的socket,代表与返回客户的TCP连接。
失败,则返回INVALID SOCKET。
accept的使用
sockaddr_in addrClient;
memset(&addrClient, 0, sizeof(SOCKADDR));
int len = sizeof(SOCKADDR);
SOCKET c = accept(s, (SOCKADDR*)&addrClient, &len);
cout << "socket:" << c << "port:" << addrClient.sin_port << "ip:" << inet_ntoa(addrClient.sin_addr) << endl;
connect的使用
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = int_addr("127.0.0.1");
addr.sin_port = htons(8000);
if(connect(s, (SOCKADDR*)&addr, sizeof(SOCKADDR)) == SOCKET_ERROR){
cout << "connect() Error:" << WSAGetLastError() << endl;
return 0;
}
5. send函数:用send函数从TCP连接的另一端发送数据
int send(IN SOCKET s, IN const char FAR* buf, IN int len, IN int flags);
参数:
- 第一个参数即为客户端的socket描述字
- 第二个参数应用要发送数据的缓存
- 第三个实际要发送的数据长度
- 第四个一般设置为0
返回值:
- 成功返回>0成功拷贝至发送缓冲区的字节数(可能小于len),错误返回SOCKET_ERROR
send的使用
int ret = send(s, "hello", strlen("hello"), 0);
cout << ret << endl;
6. recv函数:用recv函数从TCP连接的另一端接收数据
int recv(SOCKET s, char FAR* buf, int len, int flags);
参数:
- 第一个参数即为客户端的socket描述字
- 第二个参数指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据
- 第三个参数指明buf的长度
- 第四个一般设置为0
返回值:
- 成功时返回接收到的字节数,错误返回SOCKET_ERROR,连接关闭返回0
recv的使用
char buf[100];
memset(buf, 0, 100);
int ret = recv(s, buf, 100, 0);
cout << ret << "," << buf << endl;
7. sendto函数:从UDP连接的另一端发送数据
int sendto(int s, const void* buf, int len, unsigned int flags, const struct sockaddr* to, int tolen);
参数:
- 第一个参数即为客户端的socket描述字
- 第二个参数应用要发送数据的缓存
- 第三个实际要发送的数据长度
- 第四个一般设置为0
- 第五个要发向的地址
- 第六个数据发的长度
返回值:
- 成功返回>0成功拷贝至发送缓冲区的字节数(可能小于len),错误返回SOCKET_ERROR
sendto的使用
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
addr.sin_port = htons(8000);
int len = sizeof(SOCKADDR);
int ret = sendto(s, "hello", strlen("hello"), 0, (SOCKADDR*)&addr, len);
cout << ret << endl;
8. recvfrom函数:都用recv函数从UDP连接的另一端接收数据
int recvfrom(SOCET s, char FAR* buf, int len, int flags, struct sockaddr FAR* from, int FAR* fromlen);
参数:
- 第一个参数即为客户端的socket描述字
- 第二个参数指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据
- 第三个参数指明buf的长度
- 第四个一般设置为0
- 第五个接受对方的地址
返回值:
- 成功时返回接收到的字节数,错误返回SOCKET_ERROR
recvfrom的使用
char buf[100];
memset(buf, 0, 100);
int ret = recvfrom(s, buf, 100, 0, (SOCKADDR*)&addrClient, &len);
cout << ret << "," << inet_ntoa(addrClient.sin_addr) << endl;
9. shutdown函数:禁止在指定的套接字上发送和接收数据.并不会关闭套接字,在调用closesocket()函数之前,所有与该套接字相关的资源都不会被释放。
int shutdown(SOCKET s, int how);
参数:
- 第一个参数即为客户端的socket描述字
- how的方式有三种:
- SD_RECEIVE表明关闭接收通道,在该socket上不能再接收数据,如果当前接收缓存中仍有未取出数据或者以后再有数据到达,则TCP会向发送端发送RST包,将连接重置。
- SD_SEND表明关闭发送通道,TCP会将发送缓存中的数据都发送完毕并在收到所有数据的ACK后向对端发送FIN包,表明本端没有更多数据发送。这个是一个优雅关闭过程。
- SD_BOTH则表示同时关闭接收通道和发送通道。
返回值:
- 成功时返回0错误返回SOCKET_ERROR
10. closesocket函数:关闭一个套接字
int closesocket(SOCKET s);
参数:
- 第一个参数即为要关闭的socket描述字
返回值:
- 成功时返回0,错误返回SOCKET_ERROR
11. setsockopt函数:设置套接口的选项
int setsockopt(SOCKET s, int level, int optname, const char *optval, int optlen);
参数:
- 第一个参数即为socket描述字
- 第二个选项定义的层次;目前仅支持SOL_SOCKET和IPPROTO_TCP层次
- 第三个需设置的选项
- 第四个指向存放选项值的缓冲区
- 第五个缓冲区长度
返回值:
- 成功时返回0,错误返回SOCKET_ERROR
// 在send(),recv()过程中有时网络状况等原因,收发不能预期进行,可以设置收发时限:
int nNetTimeout = 1000; // 1秒
// 发送实现
setsockopt(socket, SOL_SOCKET, SO_SENDTIMEO, (char*)&nNetTimeout, sizeof(int));
// 接收时限
setsockopt(socket, SOL_SOCKET, SO_REVTIMEO,(char*)&nNetTimeout, sizeof(int));
// 设置调用closesocket()后,仍可继续重用该socket。调用closesocket()一般不会立即关闭socket,而经历TIME_WAIT的过程。
BOOL bReuseaddr = TRUE;
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*)&bReuseaddr, sizeof(BOOL));
//在send()的时候,返回的是实际发出出的字节(同步)或发送到socket缓冲区的字节(异步);系统默认的状态发送和接收一次为8688字节(约为8.5K);在实际的过程中如果发送或是接收的数据量比较大,可以设置socket缓冲区,避免send(),recv()不断的循环收发:
int nRecvBuf = 32*1024; // 设置为32K
setsockopt(s, SOL_SOCKET, SO_RCVBUF, (const char*)&nRecvBuf, sizeof(int));
// 在send(),recv()过程中有时由于网络状况等原因,收发不能预期进行,可以设置收发时限:
int nNetTimeout = 1000; // 1秒
// 发送时限
setsockopt(socket, SOL_SOCKET, SO_SENDTIMEO, (char *)&nNetTimeout, sizeof(int));
// 接收时限
setsockopt(socket, SOL_SOCK, SO_REVTIMEO,(char *)&nNetTimeout, sizeof(int));
// 设置该套接字为广播类型
bool bOpt = true;
setsockopt(connect_socket, SOL_SOCKET, SO_BROADCAST, (char*)&bOpt, sizeof(bOpt));
12. getsockopt函数:获取套接口的选项的值
int getsockopt(SOCKET s, int level, int optname, char * optval, int* optlen);
参数:
- 第一个参数即为socket描述字
- 第二个选项定义的层次;支持SOL_SOCKET和IPPROTO_TCP层次
- 第三个需获取的选项
- 第四个指向存放选项值的缓冲区
- 第五个缓冲区长度
返回值:
- 成功时返回0,错误返回SOCKET_ERROR
// 调用getsockopt(),获取SO_ACCEPTCONN参数值
int optVal=0;
int optLen = sizeof(int);
if(getsockopt(ListenSocket,
SOL_SOCKET,
SO_ACCEPTCONN,
(char*)&optVal,
&optLen)!=SOCKET_ERROR)
{
cout << optVal << endl;
}
// 调用getsockopt(),获取SO_RCVBUF参数值
int optVal = 0;
int optLen = sizeof(int);
int n=getsockopt(s, SOL_SOCKET, SO_REVBUF, (char *)&optVal, &optLen);