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
函数原型 | 功能描述 |
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
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
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
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
编程实验: Windows 中的多路复用服务端
// Select-Server.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include
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 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;
}
思考:如何编写可以跨平台编译运行的网络程序?