socket 介面已普遍存在於現代作業系統中
- Windows 下的 socket 程式設計介面與 Linux 中幾乎相同
不同之處
- 返回型別不同(控制程式碼型別)
- 控制程式碼不是檔案描述符,Window 中並不是一切接檔案 (因此 windows 下對於 socket 無法使用 send、recv)
Windows 下 socket() 的用法
SOCKET s = {0};
s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); // IPPROTO_TCP 明確指明建立 TCP 協議的套接字
if (s == INVALID) { // 建立套接字時出錯,返回 INVALID_SOCKET
ERROR("...");
return -1;
}
Windows 網路程式設計介面
#include <winsock2.h>
函式原型 | 功能描述 |
SOCKET socket(int af, int type, int protocal); | 建立套接字,為網路連線做準備 |
int connect(SOCKET s, const struct sockaddr *addr, int len); | 連線指定地址的遠端裝置 |
int send(SOCKET s, const char *buf, int len, int flags); | 傳送資料到遠端裝置 |
int recv(SOCKET s char *buf, int len, int flags); | 接收遠端裝置發回的資料 |
int closesocket(SOCKET s); | 關閉連線,銷燬套接字 |
int bind(SOCKET s, const struct sockaddr *addr, int len); | 將套接字與指定地址進行關聯 |
int listen(SOCKET s, int backlog); | 將套接字推入監聽狀態,等待連線 |
SOCKET accept(SOCKET s, struct sockaddr *addr, int len); | 接收客戶端連線 |
int shutdown(SOCKET s, int howto); | 關閉連線,停止傳送和接收 |
closes 與 shutdown(Linux 下也存在) 的區別:shoutdown 不釋放 socket 資源
幾點細微差別
- 通過 WSAStartup() 初始化系統環境(最先呼叫)
- socket(), accept() 錯誤返回 INVALID_SOCKET (不可預設為 -1)
- bind(), listen() 錯誤返回 SOCKET_ERROR (不可預設為 -1)
- connect(), send(),recv() 錯誤返回 SOCKET_ERROR (不可預設為 -1)
- 通過 WSACleanup() 清除系統環境(最後呼叫)
Windows 網路程式設計的特殊說明
- 在工程屬性中設定連結 ws2_32.lib
- 定義變數 WSADATA wd
選擇 socket 版本並初始化 WSAStartup(MAKEWORD(2, 2), &wd)
Windows 中存在多個 socket 版本
- MAKEWORD(1, 2) // 主版本為1, 副版本為2,返回0x0201
- MAKEWORD(2, 2) // 主版本為2, 副版本為2,返回0x0202
程式設計實驗:Windows 網路程式設計示例
client.c
#include "stdafx.h"
#include <winsock2.h>
int _tmain(int argc, _TCHAR* argv[])
{
SOCKET sock = 0; // 注意 socket 型別
struct sockaddr_in addr = {0};
int len = 0;
char buf[128] = {0};
char input[32] = {0};
int r = 0;
WSADATA wd = {0}; // 注意變數定義
if( WSAStartup(MAKEWORD(2, 2), &wd) != 0 ) // 注意初始化系統環境
{
printf("startup error\n");
return -1;
}
sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
if( sock == INVALID_SOCKET ) // 注意返回值型別
{
printf("socket error\n");
return -1;
}
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
addr.sin_port = htons(8888);
if( connect(sock, (struct sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR ) // 注意返回值型別
{
printf("connect error\n");
return -1;
}
printf("connect success\n");
while( 1 )
{
printf("Input: ");
scanf("%s", input);
len = send(sock, input, strlen(input) + 1, 0);
r = recv(sock, buf, sizeof(buf), 0);
if( r > 0 )
{
printf("Receive: %s\n", buf);
}
else
{
break;
}
}
closesocket(sock); // 注意
WSACleanup(); // 注意清除系統環境
return 0;
}
server.c
// Server.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <winsock2.h>
int _tmain(int argc, _TCHAR* argv[])
{
SOCKET server = 0;
struct sockaddr_in saddr = {0};
SOCKET client = 0;
struct sockaddr_in caddr = {0};
int asize = 0;
int len = 0;
char buf[32] = {0};
int r = 0;
WSADATA wd = {0};
if( WSAStartup(MAKEWORD(2, 2), &wd) != 0 )
{
printf("startup error\n");
return -1;
}
server = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
if( server == INVALID_SOCKET )
{
printf("server socket error\n");
return -1;
}
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = htonl(INADDR_ANY);
saddr.sin_port = htons(8888);
if( bind(server, (struct sockaddr*)&saddr, sizeof(saddr)) == SOCKET_ERROR )
{
printf("server bind error\n");
return -1;
}
if( listen(server, 1) == SOCKET_ERROR )
{
printf("server bind error\n");
return -1;
}
printf("server start success\n");
while( 1 )
{
asize = sizeof(caddr);
client = accept(server, (struct sockaddr*)&caddr, &asize);
if( client == INVALID_SOCKET )
{
printf("client accept error\n");
return -1;
}
printf("client: %d\n", client);
do
{
r = recv(client, buf, sizeof(buf), 0);
if( r > 0 )
{
printf("Receive: %s\n", buf);
if( strcmp(buf, "quit") != 0 )
{
len = send(client, buf, r, 0);
}
else
{
break;
}
}
} while ( r > 0 );
closesocket(client);
}
closesocket(server);
WSACleanup();
return 0;
}
問題:select() 是 Linux 系統特有的嗎?
Windows 下的 select() 函式
- Windows 中同樣提供 select() 函式,並且引數與Linux的版本完全相同
- 注意:Windows 中 select() 函式,第一個引數沒有任何意義(僅為了相容)
#include <winsock2.h>
int select(int nfds, fd_set *readfds,
fd_set *writefds,
fd_set *excepfds,
const struct timeval *timeout);
一個細微差別:Windows 中的 select() 專門為套接字設計
- fd_count 用於記錄興趣的 socket 數量
- fd_array 用於記錄感興趣的 socket 控制程式碼
typedef struct fd_set {
u_int fd_count;
SOCKET fd_array[FD_SETSIZE];
}fd_set;
Windows 中 select() 函式使用示例
temps = reads;
timeout.tv_sec = 0;
timeout.tv_usec = 10000;
num = select(0, &temps, 0, 0, &timeout);
if (num > 0) {
unsigned int i = 0;
for (i=0; i <reads.fd_fount; ++i) {
if (FD_ISSET(reads.fd_array[i], &temps)) {
if (reads.fd_array[i] == server) {
}
else {
}
}
}
}
程式設計實驗: Windows 中的多路複用服務端
// Select-Server.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <winsock2.h>
SOCKET server_handler(SOCKET server)
{
struct sockaddr_in addr = {0};
int asize = sizeof(addr);
return accept(server, (struct sockaddr*)&addr, &asize);
}
int client_handler(SOCKET client)
{
char buf[32] = {0};
int ret = recv(client, buf, sizeof(buf)-1, 0);
if( ret > 0 )
{
buf[ret] = 0;
printf("Receive: %s\n", buf);
if( strcmp(buf, "quit") != 0 )
{
ret = send(client, buf, ret, 0);
}
else
{
ret = -1;
}
}
return ret;
}
int _tmain(int argc, _TCHAR* argv[])
{
SOCKET server = 0;
struct sockaddr_in saddr = {0};
// unsigned int max = 0;
int num = 0;
fd_set reads = {0};
fd_set temps = {0};
struct timeval timeout = {0};
WSADATA wd = {0};
if( WSAStartup(MAKEWORD(2, 2), &wd) != 0 )
{
printf("startup error\n");
return -1;
}
server = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
if( server == INVALID_SOCKET )
{
printf("server socket error\n");
return -1;
}
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = htonl(INADDR_ANY);
saddr.sin_port = htons(8888);
if( bind(server, (struct sockaddr*)&saddr, sizeof(saddr)) == SOCKET_ERROR )
{
printf("server bind error\n");
return -1;
}
if( listen(server, 1) == SOCKET_ERROR )
{
printf("server listen error\n");
return -1;
}
printf("server start success\n");
FD_ZERO(&reads);
FD_SET(server, &reads);
// max = server;
while( 1 )
{
temps = reads;
timeout.tv_sec = 0;
timeout.tv_usec = 10000;
// num = select(max+1, &temps, 0, 0, &timeout);
num = select(0, &temps, 0, 0, &timeout);
if( num > 0 )
{
unsigned int i = 0;
for(i=0; i<reads.fd_count; i++) // 此處相對 Linux 的優勢,只需遍歷實際監聽的 socket 控制程式碼數量!!
{
SOCKET sock = reads.fd_array[i];
if( FD_ISSET(sock, &temps) )
{
if( sock == server )
{
SOCKET client = server_handler(sock);
if( client != INVALID_SOCKET )
{
FD_SET(client, &reads);
// max = (client > max) ? client : max;
printf("accept client: %d\n", client);
}
}
else
{
int r = client_handler(sock);
if( r == -1 )
{
FD_CLR(sock, &reads);
closesocket(sock);
}
}
}
}
}
}
closesocket(server);
WSACleanup();
return 0;
}
思考:如何編寫可以跨平臺編譯執行的網路程式?