文章目录

  • 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查看监听状态

基于套接字的循环自主相互通信java代码 套接字编程的api函数_socket

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);