套接字
socket,套接字,它是计算机之间进行通信的一种约定或一种方式。通过 socket 这种约定,一台计算机可以接收其他计算机的数据,也可以向其他计算机发送数据。
Linux下的套接字
Linux中创建的文件都有一个 int 类型的编号,称为文件描述符(File Descriptor)。使用文件时,只要知道文件描述符就可以。socket 也被认为是文件的一种,和普通文件的操作没有区别
Windows下的套接字
WinSock(Windows Socket)编程依赖于系统提供的动态链接库(DLL),有两个版本:
较早的DLL是 wsock32.dll,大小为 28KB,对应的头文件为 winsock1.h;
最新的DLL是 ws2_32.dll,大小为 69KB,对应的头文件为 winsock2.h。
加载ws2_32.dll
#pragma comment (lib, "ws2_32.lib")
WSAStartup()
使用DLL之前,还需要调用 WSAStartup() 函数进行初始化,以指明 WinSock 规范的版本
int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);
- wVersionRequested 为 WinSock 规范的版本号,低字节为主版本号,高字节为副版本号(修正版本号)
- lpWSAData 为指向 WSAData 结构体的指针。
eg:
WSADATA wsaData; WSAStartup(MAKEWORD(2, 2), &wsaData);
建立Socket
Linux
int socket(int af, int type, int protocol);
af ,地址族(Address Family),常用AF_INET(IPv4) 和 AF_INET6(IPv6)。
type ,数据传输方式,常用的有 SOCK_STREAM(面向连接)和 SOCK_DGRAM(无连接)
protocol 表示传输协议,常用的有 IPPROTO_TCP(TCP协议) 和 IPPTOTO_UDP(UDP协议)
1.TCP socket
int tcp_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
2.UDP socket
int udp_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
Windows
SOCKET sock = socket(AF_INET, SOCK_STREAM, 0); //创建TCP套接字
bind() 函数
将套接字与特定的IP地址和端口绑定起来
int bind(int sock, struct sockaddr *addr, socklen_t addrlen); //Linux int bind(SOCKET sock, const struct sockaddr *addr, int addrlen); //Windows
示例
//创建套接字 int serv_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); //创建sockaddr_in结构体变量 struct sockaddr_in serv_addr; memset(&serv_addr, 0, sizeof(serv_addr)); //每个字节都用0填充 serv_addr.sin_family = AF_INET; //使用IPv4地址 serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //具体的IP地址 serv_addr.sin_port = htons(1234); //端口 //将套接字和IP、端口绑定 bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
1、sockaddr_in 结构体
struct sockaddr_in{ sa_family_t sin_family; //地址族(Address Family),也就是地址类型 uint16_t sin_port; //16位的端口号 struct in_addr sin_addr; //32位IP地址 char sin_zero[8]; //不使用,一般用0填充 };
- sin_family 和 socket() 的第一个参数的含义相同,取值也要保持一致。
- sin_prot 为端口号。需要用 htons() 函数转换
- sin_addr 是
struct in_addr
结构体类型的变量in_addr
结构体
struct in_addr{ in_addr_t s_addr; //32位的IP地址 };
需要inet_addr()
转换
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
2、sockaddr
bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
bind()
第二个参数的类型为 sockaddr
,而代码中却使用 sockaddr_in
,然后再强制转换为 sockaddr
,这是为什么呢?
sockaddr 结构体的定义如下:
struct sockaddr{ sa_family_t sin_family; //地址族(Address Family),也就是地址类型 char sa_data[14]; //IP地址和端口号 };
下图是 sockaddr 与 sockaddr_in 的对比(括号中的数字表示所占用的字节数):
sockaddr 和 sockaddr_in 的长度相同,都是16字节,只是将IP地址和端口号合并到一起,用一个成员 sa_data 表示。要想给 sa_data 赋值,必须同时指明IP地址和端口号,例如”127.0.0.1:80“,遗憾的是,没有相关函数将这个字符串转换成需要的形式,也就很难给 sockaddr 类型的变量赋值,所以使用 sockaddr_in 来代替。这两个结构体的长度相同,强制转换类型时不会丢失字节,也没有多余的字节。
connect() 函数
建立连接
与bind类似
int connect(int sock, struct sockaddr *serv_addr, socklen_t addrlen); //Linux int connect(SOCKET sock, const struct sockaddr *serv_addr, int addrlen); //Windows
linsten() 函数
套接字进入被动监听状态
int listen(int sock, int backlog); //Linux int listen(SOCKET sock, int backlog); //Windows
- sock 需要进入监听状态的套接字
- backlog 请求队列的最大长度
accept() 函数
套接字处于监听状态时,接收客户端请求,返回新的套接字
int accept(int sock, struct sockaddr *addr, socklen_t *addrlen); //Linux SOCKET accept(SOCKET sock, struct sockaddr *addr, int *addrlen); //Windows
- sock 为服务器端套接字
- addr 为 sockaddr_in 结构体变量
- addrlen 为参数 addr 的长度,可由 sizeof() 求得
Linux 接受和发送数据
write()
写入数据
ssize_t write(int fd, const void *buf, size_t nbytes);
- fd 为要写入的文件的描述符
- buf 为要写入的数据的缓冲区地址
- nbytes 为要写入的数据的字节数。
read()
读取数据
ssize_t read(int fd, void *buf, size_t nbytes);
- fd 为要读取的文件的描述符
- buf 为要接收数据的缓冲区地址
- nbytes 为要读取的数据的字节数。
Windos 接受和发送数据
send()
发送数据
int send(SOCKET sock, const char *buf, int len, int flags);
- sock 为要发送数据的套接字
- buf 为要发送的数据的缓冲区地址
- len 为要发送的数据的字节数
- flags 为发送数据时的选项,一般设置为 0 或 NULL
recv()
接受数据
int recv(SOCKET sock, char *buf, int len, int flags);
同上
示例
Windows
server
#include <stdio.h> #include <winsock2.h> #pragma comment (lib, "ws2_32.lib") //加载 ws2_32.dll int main(){ //初始化 DLL WSADATA wsaData; WSAStartup(MAKEWORD(2, 2), &wsaData); //创建套接字 PF_INET:IPv4 SOCKET servSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); //绑定套接字 sockaddr_in sockAddr; memset(&sockAddr, 0, sizeof(sockAddr)); //每个字节都用0填充 sockAddr.sin_family = PF_INET; //使用IPv4地址 sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); //具体的IP地址 sockAddr.sin_port = htons(1234); //端口 bind(servSock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR)); //进入监听状态 listen(servSock, 20); //接收客户端请求 SOCKADDR clntAddr; int nSize = sizeof(SOCKADDR); SOCKET clntSock = accept(servSock, (SOCKADDR*)&clntAddr, &nSize); //向客户端发送数据 char *str = "Hello World!"; send(clntSock, str, strlen(str)+sizeof(char), NULL); //关闭套接字 closesocket(clntSock); closesocket(servSock); //终止 DLL 的使用 WSACleanup(); return 0; }
client
#include <stdio.h> #include <stdlib.h> #include <WinSock2.h> #pragma comment(lib, "ws2_32.lib") //加载 ws2_32.dll int main(){ //初始化DLL WSADATA wsaData; WSAStartup(MAKEWORD(2, 2), &wsaData); //创建套接字 SOCKET sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); //向服务器发起请求 sockaddr_in sockAddr; memset(&sockAddr, 0, sizeof(sockAddr)); //每个字节都用0填充 sockAddr.sin_family = PF_INET; sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); sockAddr.sin_port = htons(1234); connect(sock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR)); //接收服务器传回的数据 char szBuffer[MAXBYTE] = {0}; recv(sock, szBuffer, MAXBYTE, NULL); //输出接收到的数据 printf("Message form server: %s ", szBuffer); //关闭套接字 closesocket(sock); //终止使用 DLL WSACleanup(); system("pause"); return 0;
Linux
服务器端代码 server.cpp
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #include <netinet/in.h> int main(){ //创建套接字 int serv_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); //将套接字和IP、端口绑定 struct sockaddr_in serv_addr; memset(&serv_addr, 0, sizeof(serv_addr)); //每个字节都用0填充 serv_addr.sin_family = AF_INET; //使用IPv4地址 serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //具体的IP地址 serv_addr.sin_port = htons(1234); //端口 bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); //进入监听状态,等待用户发起请求 listen(serv_sock, 20); //接收客户端请求 struct sockaddr_in clnt_addr; socklen_t clnt_addr_size = sizeof(clnt_addr); int clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size); //向客户端发送数据 char str[] = "Hello World!"; write(clnt_sock, str, sizeof(str)); //关闭套接字 close(clnt_sock); close(serv_sock); return 0; }
客户端代码 client.cpp:
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> int main(){ //创建套接字 int sock = socket(AF_INET, SOCK_STREAM, 0); //向服务器(特定的IP和端口)发起请求 struct sockaddr_in serv_addr; memset(&serv_addr, 0, sizeof(serv_addr)); //每个字节都用0填充 serv_addr.sin_family = AF_INET; //使用IPv4地址 serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //具体的IP地址 serv_addr.sin_port = htons(1234); //端口 connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); //读取服务器传回的数据 char buffer[40]; read(sock, buffer, sizeof(buffer)-1); printf("Message form server: %s ", buffer); //关闭套接字 close(sock); return 0; }
参考
)