目录
何为socket
Socket本身也是进程间通信方式的一种。但是与其他的方式不同,socket可以用于不同主机间的进程通信,这得益于它的创建方式和工作流程。在之前介绍TCP和UDP的文章中,端口作为首部中的一部分被提及,但并未进行详细的描述。实际上端口代表的就是一个进程,而一些特定的进程都有其固定的端口号,如DNS使用的默认端口号为53、通过HTTP向Web服务器请求网页的默认端口号为80等。而套接字的作用就是将端口与进程绑定,从而使发往指定端口的数据能够正确的被相应的进程接收。因此socket的本质就是传输层对应用层提供的接口,通常服务器端与客户端所使用的socket是不同的。在使用TCP时,服务器端会一直存在一个用于监听是否有客户端发起新的连接的socket,这种socket被称之为master socket;当确认需要建立连接时,双方便会各自新建一个用于传输数据的socket,这种socket则被称之为connected socket。在使用UDP时,不会建立真实的连接,而只是在收发数据时指定相应的地址和端口。
使用socket
在进程使用socket进行各种操作时,就需要用到其暴露给应用层的各种接口,即socket API。在不同操作系统环境下使用不同语言操作socket的方法都各有不同,在这里主要介绍使用C/C++时的socket API。而通过TCP和UDP进行通信时,使用socket的流程也是不一样的。
一、C/C++ socket API
在Linux和Windows环境下,使用socket时需要引入的头文件各不相同,使用的流程、API、数据类型和一些结构体的结构也有所不同,在具体使用时需要深入了解,这里只讨论在Windows环境下对Windows Socket 2的使用。在使用之前,需要在引入相关的静态库 (Ws2_32.lib,或是动态载入Ws2_32.ddl) 后再引入头文件winsock2.h。在操作socket之前,需要调用其中的WSAStartUp() 进行初始化,而在使用完Windows Socket 2之后,需要调用WSACleanup()进行收尾。想要了解更多Windows Socket 2中API的具体使用方法可以阅读官方文档,以下部分仅节选了几个常用的API进行简单的描述:
1. 创建socket
/*
方法
*/
// 创建socket
SOCKET WSAAPI socket(
[in] int af, // address family,指定地址簇的枚举
[in] int type, // 指定socket类型的枚举
[in] int protocol // 指定使用协议的枚举
); // 若成功则返回描述符,否则返回INVALID_SOCKET
// 绑定 (无强制规定但通常只有客户端使用)
// 由于服务器端的进程通常使用固定端口,因此通常必须绑定
// 由于客户端的进程通常使用随机端口,因此通常不会绑定,否则需要确认当前端口是否已被占用
int WSAAPI bind(
[in] SOCKET s, // 未绑定socket的描述符
[in] const sockaddr *name, // sockaddr结构体的位置,对不同地址簇其含义不同
[in] int namelen // sockaddr结构体的长度
); // 若成功则返回0,否则返回SOCKET_ERROR
/*
简单示例
*/
// 声明变量
SOCKET sock; // 声明一个socket
struct sockaddr_in saServer; // 声明一个sockaddr_in结构体
hostent* localHost; // 声明一个指向本地主机的指针
char* localIP; // 声明一个指向本地主机IP地址的指针
// 创建一个在TCP中使用IPv4地址簇的socket
sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
// 获取本地主机信息
localHost = gethostbyname("");
localIP = inet_ntoa (*(struct in_addr *)*localHost->h_addr_list);
// 更新sockaddr_in结构体
saServer.sin_family = AF_INET;
saServer.sin_addr.s_addr = inet_addr(localIP);
saServer.sin_port = htons(5150);
// 使用sockaddr_in结构体中的内容绑定socket
// 传参时将sockaddr_in结构体类型转换成sockaddr结构体
bind(sock, (SOCKADDR*) & saServer, sizeof (saServer));
2. 建立连接(仅限TCP)
/*
方法
*/
// 监听 (服务器端)
int WSAAPI listen(
[in] SOCKET s, // 已绑定但未连接的socket的描述符
[in] int backlog // 挂起连接的队列的最大长度
); // 若成功则返回0,否则返回SOCKET_ERROR
// 同意连接 (服务器端)
SOCKET WSAAPI accept(
[in] SOCKET s, // 已开启监听的socket的描述符
[out] sockaddr *addr, // 保存客户端信息的sockaddr结构体的位置,可选
[in, out] int *addrlen // sockaddr结构体的长度,可选
); // 若成功则返回一个已连接的新socket的描述符,
// 否则返回INVALID_SOCKET
// 建立连接 (客户端)
int WSAAPI connect(
[in] SOCKET s, // 已绑定但未连接的socket的描述符
[in] co