简介
Berkeley Socket套接字
套接字( Socket)最初是由加利福尼亚大学Berkeley分校为UNIX操作系统开发的网络通信接口,20世纪80年代初,加利福尼亚大学 Berkeley将美国国防部高研署提供的tC/iP集成到Unix中,并很快开发了TCP/IP应用程序接口(API),即 Socket(套接字)接口随着UNIX操作系统的广泛使用,套接字成为当前最流行的网络通信应用程序接口之一。
WinSocket套接字
90年代初,由 Sun Microsystems, JSB Corporation, FTP software, Microdyne和 MicrosoftW等几家公司共同制定了一套标准,即 Sockets规范。它是 Berkeley Sockets的重要扩充,主要体现在它增加了一些异步函数和符合 Windows消息驱动特性的网络事件异步选择机制。 Windows Sockets规范是一套开放的、支持多种协议的 Windows下的网络编程接口。目前实际应用中的 Windows Sockets规范主要有1.1版和2.0版,其中1.1版只支持TCP/IP协议,而2.0版支持多协议,并具有良好的向后兼容性。
创建流程
客户端
初始化套接字
1 2 3
| WSADATA wsadata = { 0 }; int result = WSAStartup(MAKEWORD(2, 2), &wsadata); check_result(!result && wsadata.wVersion == 0x0202, "套接字环境初始化失败!");
|
建立socket
1 2
| SOCKET client = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); check_result(client != INVALID_SOCKET, "套接字创建失败!");
|
- domain 通常为PF_INET,表示互联网协议(TCP/IP)
- type 指定了Socket的类型 SOCK_STREAM(TCP),SOCK_DGRAM(UDP)
- protocol 通常赋值为0
绑定socket
1 2 3 4 5 6
| SOCKADDR_IN server_addr = { 0 }; server_addr.sin_family = AF_INET; server_addr.sin_port = htons(0x1515); inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr); result = connect(client, (SOCKADDR*)&server_addr, sizeof(server_addr)); check_result(result != SOCKET_ERROR, "套接字绑定失败!");
|
注意*sin_port*和*sin_addr*需要转换成网络字节优先顺序
客户端首发消息
1 2 3 4
| char buffer[0x100] = { 0 }; recv(client, buffer, 0x100, 0); printf("server: %s\n", buffer);
|
面向连接数据的发送
1
| int send(int sockfd, const void *msg, int len, int flags);
|
- sockfd 监听的套接字
- msg 指向要发送的数据
- len 以字节为单位的数据长度
- flags 一般设置为0
- 返回值为实际发送出去的字节数
面向连接的数据接收
1
| int recv(int sockfd, void *buf, int len, int flags);
|
- sockfd 监听的套接字
- buf 存放接收数据的缓冲区
- len 以字节为单位的数据长度
- flags 一般设置为0
- 返回值为实际接收到的数据
无连接的数据发送
int sendto(int sockfd, const void *msg, int len, int flags, const struct sockaddr \*to, int tolen);**
**
这个函数比*send()*函数多了两个参数
- to 要发送数据到的目的主机的IP地址和端口号信息
- tolen 通常别赋值为
sizeof(struct sockaddr)
- 返回值为实际发送出去的字节数
无连接的数据接收
int recvfrom(int sockfd, void *buf, int len, int flag, struct sockaddr *from, int \*fromlen);**
**
这个函数比*recv()*函数多了几个参数
- from 是一个struct sockaddr类型的变量,保存数据来源主机的IP地址和端口号
- fromlen 一般设置为
sizeof(stuct sockaddr)
- 返回值为实际接收到的数据
关闭套接字
1 2
| closesocket(client); WSACleanup();
|
- 停止socket上面的全部操作
closesocket(sockfd);
- 关闭socket上面的某一个操作
int shutdown(int sockfd, int how);
how有几个可选的值
- 0:不允许继续接收数据
- 1:不允许接续发送数据
- 2:不允许继续发送和接收数据
服务端
初始化
1 2 3 4
| WSADATA wsadata = { 0 }; int result = WSAStartup(MAKEWORD(2, 2), &wsadata); check_result(!result && wsadata.wVersion == 0x0202, "套接字环境初始化失败!");
|
创建套接字
1 2 3
| SOCKET server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); check_result(server != INVALID_SOCKET, "套接字创建失败!");
|
绑定
1 2 3 4 5 6
| SOCKADDR_IN server_addr = { 0 }; server_addr.sin_family = AF_INET; server_addr.sin_port = htons(0x1515); inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr); result = bind(server, (SOCKADDR*)&server_addr, sizeof(server_addr)); check_result(result != SOCKET_ERROR, "套接字绑定失败!");
|
监听
1 2
| result = listen(server, SOMAXCONN); check_result(result != SOCKET_ERROR, "套接字监听失败!");
|
服务器端程序调用listern()函数使得socket处于一个别动监听的模式,并且为这个socket建立一个输入数据队列,将到达服务器的请求保存到此队列中,直到程序处理。
int listen(int sockfd, int backlog);
- sockfd 调用socket()函数返回的socket套接字
- backlog 指定在请求队列中允许的最大请求数
- 缓存队列中的请求,等待accept处理
等待客户端连接
1 2 3 4
| int size = sizeof(SOCKADDR_IN); SOCKADDR_IN client_addr = { 0 }; SOCKET client = accept(server, (SOCKADDR*)&client_addr, &size); check_result(client != INVALID_SOCKET, "客户端接收失败!");
|
建立好缓存队列后,服务器端程序可以调用accept()函数处理客户的连接请求。
int accept(int sockfd, void *addr, int *addrlen);
- sockfd 被监听的socket套接字
- addr 通常是一个指向sockaddr_in变量的指针,该变量用于存储提出连接请求的主机信息
- addrlen 通常是一个指向值为
sizeof(struct sockaddr_in)
的整型指针变量
关闭套接字
1 2 3 4 5
| closesocket(client); closesocket(server);
WSACleanup();
|
案例
案例1
聊天室服务器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
| #include <WinSock2.h> #pragma comment(lib, "ws2_32.lib")
#include <map> #include <stdio.h> #include <string> #include <algorithm> #include <ws2tcpip.h> using namespace std;
map<SOCKET, string> clients;
void check_result(bool result, const char* msg) { if (result == false) { printf("error: %s\n", msg); system("pause"); exit(0); } }
DWORD CALLBACK RecvThread(LPVOID param) { SOCKET client = (SOCKET)param; char buffer[0x100] = { 0 };
while (recv(client, buffer, 0x100, 0) > 0) { for (auto& sock : clients) { if (sock.first == client) continue; string msg = clients[client] + ": " + buffer;
send(sock.first, msg.c_str(), msg.length() + 1, 0); } }
printf("%s 离开了服务器\n", clients[client].c_str());
return 0; }
int main() { WSADATA wsadata = { 0 }; int result = WSAStartup(MAKEWORD(2, 2), &wsadata); check_result(!result && wsadata.wVersion == 0x0202, "套接字环境初始化失败!");
SOCKET server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); check_result(server != INVALID_SOCKET, "套接字创建失败!");
SOCKADDR_IN server_addr = { 0 }; server_addr.sin_family = AF_INET; server_addr.sin_port = htons(0x1515); inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr); result = bind(server, (SOCKADDR*)&server_addr, sizeof(server_addr)); check_result(result != SOCKET_ERROR, "套接字绑定失败!");
result = listen(server, SOMAXCONN); check_result(result != SOCKET_ERROR, "套接字监听失败!");
while (TRUE) { int size = sizeof(SOCKADDR_IN); SOCKADDR_IN client_addr = { 0 }; SOCKET client = accept(server, (SOCKADDR*)&client_addr, &size); check_result(client != INVALID_SOCKET, "客户端接收失败!");
char nickname[0x100] = { 0 }; recv(client, nickname, 0x100, 0);
printf("%s 连接到了服务器\n", nickname); clients[client] = nickname;
CreateThread(NULL, NULL, RecvThread, (LPVOID)client, NULL, NULL); }
closesocket(server);
WSACleanup();
system("pause"); return 0; }
|
聊天室客户端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
| #include <WinSock2.h> #pragma comment(lib, "ws2_32.lib")
#include <vector> #include <stdio.h> #include <ws2tcpip.h>
void check_result(bool result, const char* msg) { if (result == false) { printf("error: %s\n", msg); system("pause"); exit(0); } }
DWORD CALLBACK RecvThread(LPVOID param) { SOCKET client = (SOCKET)param; char buffer[0x100] = { 0 }; while (recv(client, buffer, 0x100, 0) > 0) printf("%s\n", buffer);
return 0; }
int main() { WSADATA wsadata = { 0 }; int result = WSAStartup(MAKEWORD(2, 2), &wsadata); check_result(!result && wsadata.wVersion == 0x0202, "套接字环境初始化失败!");
SOCKET client = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); check_result(client != INVALID_SOCKET, "套接字创建失败!");
SOCKADDR_IN server_addr = { 0 }; server_addr.sin_family = AF_INET; server_addr.sin_port = htons(0x1515); inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr); result = connect(client, (SOCKADDR*)&server_addr, sizeof(server_addr)); check_result(result != SOCKET_ERROR, "套接字连接失败!");
char buffer[0x100] = { 0 }; scanf_s("%s", buffer, 0x100); send(client, buffer, 0x100, 0);
CreateThread(NULL, NULL, RecvThread, (LPVOID)client, NULL, NULL);
while (scanf_s("%s", buffer, 0x100) == 1 && strcmp(buffer, "quit")) send(client, buffer, strlen(buffer) + 1, 0);
closesocket(client);
WSACleanup();
system("pause"); return 0; }
|
案例2
文件发送端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
| #include <WinSock2.h> #pragma comment(lib, "ws2_32.lib")
#include <stdio.h> #include <string.h> #include <ws2tcpip.h>
const int block_size = 1024*1024;
typedef struct _MY_FILE_INFO { char name[0x100]; int block_count; } MY_FILE_INFO, * PMY_FILE_INFO;
typedef struct _BLOCK_INFO { int index; char data[block_size]; DWORD size; } BLOCK_INFO;
void check_result(bool result, const char* msg) { if (result == false) { printf("error: %s\n", msg); system("pause"); exit(0); } }
HANDLE send_file_info(SOCKET sock, LPCSTR filename, PMY_FILE_INFO info) { HANDLE file = CreateFileA(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
DWORD size = GetFileSize(file, NULL);
int count = size % block_size == 0 ? size / block_size : size / block_size + 1;
info->block_count = count; memcpy(info->name, filename, strlen(filename) + 1);
send(sock, (char*)info, sizeof(*info), 0);
return file; }
VOID send_file_data(SOCKET sock, HANDLE file, PMY_FILE_INFO info) { for (int i = 0; i < info->block_count; ++i) { BLOCK_INFO* block_info = new BLOCK_INFO{ i };
ReadFile(file, block_info->data, block_size, &block_info->size, NULL); send(sock, (char*)block_info, sizeof(BLOCK_INFO), 0);
Sleep(100); }
CloseHandle(file); }
int main() { WSADATA wsadata = { 0 }; int result = WSAStartup(MAKEWORD(2, 2), &wsadata); check_result(!result && wsadata.wVersion == 0x0202, "套接字环境初始化失败!");
SOCKET server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); check_result(server != INVALID_SOCKET, "套接字创建失败!");
SOCKADDR_IN server_addr = { 0 }; server_addr.sin_family = AF_INET; server_addr.sin_port = htons(0x1515); inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr); result = bind(server, (SOCKADDR*)&server_addr, sizeof(server_addr)); check_result(result != SOCKET_ERROR, "套接字绑定失败!");
result = listen(server, SOMAXCONN); check_result(result != SOCKET_ERROR, "套接字监听失败!");
int size = sizeof(SOCKADDR_IN); SOCKADDR_IN client_addr = { 0 }; SOCKET client = accept(server, (SOCKADDR*)&client_addr, &size); check_result(client != INVALID_SOCKET, "客户端接收失败!");
MY_FILE_INFO info = { 0 }; HANDLE file = send_file_info(client, "demo.exe", &info); send_file_data(client, file, &info);
closesocket(client); closesocket(server);
WSACleanup();
system("pause"); return 0; }
|
文件接收端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
| #include <WinSock2.h> #pragma comment(lib, "ws2_32.lib")
#include <stdio.h> #include <ws2tcpip.h>
const int block_size = 1024 * 1024;
typedef struct _MY_FILE_INFO { char name[0x100]; int block_count; } MY_FILE_INFO, *PMY_FILE_INFO;
typedef struct _BLOCK_INFO { int index; char data[block_size]; int size; } BLOCK_INFO;
void check_result(bool result, const char* msg) { if (result == false) { printf("error: %s\n", msg); system("pause"); exit(0); } }
void recv_file_info(SOCKET sock, PMY_FILE_INFO info) { recv(sock, (char*)info, sizeof(*info), 0); printf("%s %d\n", info->name, info->block_count); }
void recv_file_data(SOCKET sock, PMY_FILE_INFO info) { HANDLE file = CreateFileA(info->name, GENERIC_WRITE, NULL, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
for (int i = 0; i < info->block_count; ++i) { BLOCK_INFO* block_info = new BLOCK_INFO{ 0 }; recv(sock, (char*)block_info, sizeof(BLOCK_INFO), 0);
SetFilePointer(file, block_size * block_info->index, 0, FILE_BEGIN);
DWORD bytes = 0; WriteFile(file, block_info->data, block_info->size, &bytes, NULL);
printf("%d 写入成功,大小为 %d\n", block_info->index, bytes); }
CloseHandle(file); }
int main() { WSADATA wsadata = { 0 }; int result = WSAStartup(MAKEWORD(2, 2), &wsadata); check_result(!result && wsadata.wVersion == 0x0202, "套接字环境初始化失败!");
SOCKET client = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); check_result(client != INVALID_SOCKET, "套接字创建失败!");
SOCKADDR_IN server_addr = { 0 }; server_addr.sin_family = AF_INET; server_addr.sin_port = htons(0x1515); inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr); result = connect(client, (SOCKADDR*)&server_addr, sizeof(server_addr)); check_result(result != SOCKET_ERROR, "套接字绑定失败!");
MY_FILE_INFO info = { 0 }; recv_file_info(client, &info); recv_file_data(client, &info);
closesocket(client);
WSACleanup();
system("pause"); return 0; }
|