参考
- 《TCP/IP网络编程》 尹圣雨
套接字类型与协议设置
创建套接字
#include int socket(int domain, int type, int protocol);
成功时返回文件描述符,失败时返回-1。domain:套接字中使用的协议族(Protocol Family)信息;type:套接字数据传输类型信息;protocol:计算机通信中使用的协议信息
协议族
头文件sys/socket.h中声明的协议族:
- PF_INET:IPv4互联网协议族
- PF_INET6:IPv6互联网协议族
- PF_LOCAL:本地通信的UNIX协议族
- PF_PACKET:底层套接字的协议族
- PF_IPX:IPX Novell协议族
套接字类型
套接字类型是指套接字的数据传输方式。因为一个协议族中存在多种数据传输方式,决定了协议族不能同时决定数据传输方式,所以需要第二个参数限定
面向连接的套接字(SOCK_STREAM)
如果socket函数的第二个参数设为SOCK_STREAM,将创建面向连接的套接字。
面向连接的套接字的特点:
- 传输过程中数据不会消失
- 按序传输数据
- 传输的数据不存在数据边界(Boundary)。收发数据的套接字内部有Buffer,接收端通过read函数从Buffer提取数据,只要Buffer未超过容量,就可以不立即调用read函数;当Buffer满时,可以通过1次read函数读取所有数据,也可多次调用read函数,每次读取一部分。所以,存在数据边界的意思是,接收数据的次数应和传输次数相同
- 套接字连接必须一一
综上,面向连接的套接字是可靠的、按序传递的、基于字节的面向连接的数据传输方式的套接字
面向消息的套接字(SOCK_DGRAM)
如果socket函数的第二个参数为SOCK_DGRAM,将创建面向消息的套接字
面向消息的套接字的特点:
- 强调快速传输而非传输顺序
- 传输的数据可能丢失也可能损毁
- 传输的数据有数据边界
- 限制每次传输的数据大小
综上,面向消息的套接字是不可靠的、不按序传递的、以数据的高速传输为目的的套接字
协议的最终选择
socket的第三个参数决定最终采用的协议。因为有些时候,一个协议族下,传输方式相同,但协议不同,此时边需要第三个参数具体指定协议信息。但大部分情况下,可以像第三个参数传递0
以IPv4为例,参数PF_INET指IPv4网络协议族,SOCK_STREAM是面向连接的数据传输方式,满足这2个条件的协议只有IPPROTO_TCP,因此可以用0代替,同时,这种套接字称为TCP套接字
int tcp_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
或
int tcp_socket = socket(PF_INET, SOCK_STREAM, 0);
如果第一个参数是PF_INET,第二个参数是SOCK_DGRAM,即IPv4协议族中面向消息的套接字,满足上述条件的只有IPPROTO_UDP,因此也可以用0代替,同时,这种套接字称为UDP套接字
int udp_socket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
或
int udp_socket = socket(PF_INET, SOCK_DGRAM, 0);
Linux下TCP套接字示例
服务器端
#include
#include
#include
#include
#include
#include void error_handling(char *message);int main(int argc, char* argv[])
{int serv_sock;int clnt_sock;struct sockaddr_in serv_addr;struct sockaddr_in clnt_addr;socklen_t clnt_addr_size;char message[] = "Hello World!";if (argc != 2){printf("Usage : %s \n" , argv[0]);exit(1);}serv_sock = socket(PF_INET, SOCK_STREAM, 0);if (serv_sock == -1){error_handling("socket() error");}memset(&serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);serv_addr.sin_port = htons(atoi(argv[1]));if (bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1){error_handling("bind() error");}if (listen(serv_sock, 5) == -1){error_handling("listen() error");}clnt_addr_size = sizeof(clnt_addr);clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);if (clnt_sock == -1){error_handling("accept() error");}write(clnt_sock, message, sizeof(message));close(clnt_sock);close(serv_sock);return 0;
}void error_handling(char *message)
{fputs(message, stderr);fputc('\n', stderr);exit(1);
}
客户端
#include
#include
#include
#include
#include
#include void error_handling(char* message);int main(int argc, char* argv[])
{int sock;struct sockaddr_in serv_addr;char message[30];int str_len = 0;int idx = 0, read_len = 0;if (argc != 3){printf("Usage : %s \n" , argv[0]);exit(1);}sock = socket(PF_INET, SOCK_STREAM, 0);if (sock == -1){error_handling("socket() error");}memset(&serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = inet_addr(argv[1]);serv_addr.sin_port = htons(atoi(argv[2]));if (connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1){error_handling("connect() error!");}while (read_len = read(sock, &message[idx++], 1)){if (read_len == -1){error_handling("read() error!");}str_len += read_len;}printf("Message from server: %s \n", message);printf("Function read call count: %d \n", str_len);close(sock);return 0;
}void error_handling(char* message)
{fputs(message, stderr);fputc('\n', stderr);exit(1);
}
客户端调用了多次read函数进行读取,用于说明TCP套接字传输的数据不存在数据边界
Windows下TCP套接字示例
Windows下的socket函数
与Linux平台的socket只有返回值不同
#include SOCKET socket(int af, int type, int protocol);
返回值为socket句柄,失败时返回INVALID_SOCKET
实际上,socket函数返回整数型数据,因此可以通过int型变量接收,但考虑到扩展性,建议使用SOCKET结构体变量保存套接字句柄。另外,失败时返回的INVALID_SOCKET,实际值为-1,但也建议使用INVALID_SOCKET,防止之后微软修改INVALID_SOCKET常数值
服务器端
#include
#include
#include
void ErrorHandling(char* message);int main(int argc, char* argv[])
{WSADATA wsaData;SOCKET hServSock, hClntSock;SOCKADDR_IN servAddr, clntAddr;int szClntAddr;char message[] = "Hello World!";if (argc != 2){printf("Usage : %s \n" , argv[0]);exit(1);}if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) // 初始化套接字库{ErrorHandling("WSAStartup() error!");}hServSock = socket(PF_INET, SOCK_STREAM, 0); // 创建套接字if (hServSock == INVALID_SOCKET){ErrorHandling("socket() error");}memset(&servAddr, 0, sizeof(servAddr));servAddr.sin_family = AF_INET;servAddr.sin_addr.s_addr = htonl(INADDR_ANY);servAddr.sin_port = htons(atoi(argv[1]));if (bind(hServSock, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR) // 给该套接字分配IP地址与端口号{ErrorHandling("bind() error");}if (listen(hServSock, 5) == SOCKET_ERROR) // 使创建的套接字成为服务器端套接字{ErrorHandling("listen() error");}szClntAddr = sizeof(clntAddr);hClntSock = accept(hServSock, (SOCKADDR*)&clntAddr, &szClntAddr); // 受理客户端连接请求if (hClntSock == INVALID_SOCKET){ErrorHandling("accept() error");}send(hClntSock, message, sizeof(message), 0); // 向连接的客户端传输数据closesocket(hClntSock);closesocket(hServSock);WSACleanup(); // 注销初始化的套接字库return 0;
}void ErrorHandling(char* message)
{fputs(message, stderr);fputc('\n', stderr);exit(1);
}
客户端
#include
#include
#include
#include
void ErrorHandling(char* message);int main(int argc, char* argv[])
{WSADATA wsaData;SOCKET hSocket;SOCKADDR_IN servAddr;char message[30];int strLen = 0;int idx = 0, readLen = 0;if (argc != 3){printf("Usage : %s \n" , argv[0]);exit(1);}if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) // 初始化Winsock库{ErrorHandling("WSAStartup() error!");}hSocket = socket(PF_INET, SOCK_STREAM, 0); // 创建套接字if (hSocket == INVALID_SOCKET){ErrorHandling("socket() error");}memset(&servAddr, 0, sizeof(servAddr));servAddr.sin_family = AF_INET;//servAddr.sin_addr.s_addr = inet_addr(argv[1]);inet_pton(AF_INET, argv[1], &servAddr.sin_addr);servAddr.sin_port = htons(atoi(argv[2]));if (connect(hSocket, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR) // 通过套接字向服务器发出连接请求{ErrorHandling("connect() error!");}while (readLen = recv(hSocket, &message[idx++], 1, 0)) // 接收服务器发来的数据{if (readLen == -1){ErrorHandling("read() error!");}strLen += readLen;if (message[idx-1] == '\0'){break;}}printf("Message from server: %s \n", message);printf("Function read call count: %d \n", strLen);closesocket(hSocket);WSACleanup(); // 注销Winsock库return 0;
}void ErrorHandling(char* message)
{fputs(message, stderr);fputc('\n', stderr);exit(1);
}