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;}
思考:如何编写能够跨平台编译运行的网络程序?