Windows 环境下的 Socket 编程 1 - 环境搭建和 Socket 相关函数
版本占用 2 字节,高 8 位为副版本,低 8 位为主版本,比如 V1.2 版本,则传递 0x0201。Windows 严格区分文件 I/O 函数和 SOCKET I/O函数,而Linux只有文件 I/O 函数。编译:菜单 Terminal > Run Build Task ,或者快捷键 Ctrl+Shift+B。编译:菜单 Terminal > Run Build Task ,或者快捷键 Ctr
- 编译环境:VS Code + gcc,环境搭建可以参考这里。
- 本系列文章参考
尹圣雨
著的《TCP/IP网络编程》。
所有学习都要在开始前认识到其必要性!这是我经常挂在嘴边的一句话。从语言的基本语法到系统函数,若无法回答“这到底有何必要?”学习过程将变得枯燥无味,而且很容易遗忘。
— 尹圣雨
SOCKET 编程头文件和库
使用 Windows Socket 编程,需要 winsock2.h
头文件和 ws2_32.lib
库。
对于 gcc
编译器,加载 ws2_32.lib
库需要在编译参数中添加参数:-lwsock32
。否则 gcc
会因为没有找到库而报错(编译的时候出现
undefined reference to `__imp_WSAStartup’)。
具体添加方法为:
- 在工程文件夹下打开
.vscode
文件夹 - 打开
tasks.json
文件,在args
字段中添加新的参数-lwsock32
,如下图所示。
Windows SOCKET 初始化
以下是 Windows SOCKET
编程固定格式。
#include <winsock2.h>
int main(int argc, char *argv[])
{
WSADATA wsaData;
// ...
if(WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
// ...
exit(1);
}
// ...
WSACleanup();
return 0;
}
首先必须调用 WSAStartup
函数,设置程序中用到的 Windows SOCKET
版本,并初始化相应版本的库。
WSAStartup
函数原型为:
/*成功返回 0 ,失败返回非零错误码*/
int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);
Windows SOCKET
存在多个版本,参数 wVersionRequested
指明使用哪个版本。版本占用 2 字节,高 8 位为副版本,低 8 位为主版本,比如 V1.2 版本,则传递 0x0201。一般会使用 MAKEWORD
宏来构建版本信息:
WORD ver = MAKEWORD(1, 2); // V1.2 版本, ver = 0x0201
第二个参数 lpWSAData
用于保存库信息。
其次,当程序结束之前,需要调用 WSACleanup
注销 SOCKET
库。
Windows SOCKET 相关函数
socket
函数
/*成功返回 SOCKET 句柄,失败返回 INVALID_SOCKET*/
SOCKET socket(int af,int type,int protocol);
af
:指定 SOCKET 使用的协议族
,一个协议族
下面会有多种协议
,比如iPv4
协议族就有TCP
协议、UDP
协议等等,一个协议族
下面也有多种数据传输方式。
常见协议族有:IPv4协议族(PF_INET
)、IPv6协议族(PF_INET6
)、底层 SOCKET 的协议族(PF_PACKET
)。type
:指定 SOCKET 的数据传输方式
。
常见的数据传输方式有:面向连接的(SOCK_STREAM
)、面向消息的(SOCK_DGRAM
)
面向连接的 SOCKET :可靠的、按序传递的、基于字节的面向连接的数据传输方式
面向消息的 SOCKET:不可靠的、不按序传递的、以数据的高速传输为目的
protocol
:指定 SOCKET 使用的协议
,该协议
必须是协议族
支持的协议
之一。
常见的协议类型有:TCP 协议(IPPROTO_TCP
)、UDP 协议(IP_PROTO_UDP
)
bind
函数
/*成功返回 0 ,失败返回 SOCKET_ERROR*/
int bind(SOCKET s,const struct sockaddr *name,int namelen);
listen
函数
/*成功返回 0 ,失败返回 SOCKET_ERROR
* backlog:连接请求队列的长度,表示允许最多多少个连接请求进入队列
*/
int listen(SOCKET s,int backlog);
accept
函数
调用accept
函数时,若等待队列为空,则accept
函数不会返回,直到队列中出现新的客户端连接。
/*成功返回 SOCKET 句柄,失败返回 INVALID_SOCKET
* addr:保存客户端地址信息
*/
SOCKET accept(SOCKET s,struct sockaddr *addr,int *addrlen);
connect
函数
客户端调用connect
函数后,发生以下情况之一才会返回:
a. 服务器端接收连接请求
b. 发生断网等异常情况而中断连接请求
/*成功返回 0 ,失败返回 SOCKET_ERROR */
int connect(SOCKET s,const struct sockaddr *name,int namelen);
closesocket
函数
/*成功返回 0 ,失败返回 SOCKET_ERROR*/
int closesocket(SOCKET s);
Windows 的 I/O 函数
Windows 严格区分文件 I/O 函数和 SOCKET I/O函数,而Linux只有文件 I/O 函数。
send
函数
/*成功返回传输的字节数,失败返回 SOCKET_ERROR*/
int send(SOCKET s,const char *buf,int len,int flags);
recv
函数
/*成功返回接收的字节数(收到 EOF 时为 0 ),失败返回 SOCKET_ERROR*/
int WSAAPI recv(SOCKET s,char *buf,int len,int flags);
基于 Windoes 的服务器和客户端测试代码
- 服务器端
#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h>
void ErrorHandler(char *message);
int main(int argc, char *argv[])
{
WSADATA wsaData;
SOCKET hServSock, hClntSock;
SOCKADDR_IN servAddr, clntAddr;
int szClntAddr;
char message[] = "Hello, world!";
if(argc != 2)
{
printf ("Usage: %s <port>\n", argv[0]);
exit(1);
}
if(WSAStartup(MAKEWORD(2,2), &wsaData) != 0)
{
ErrorHandler("WSAStartup failed");
}
hServSock = socket(PF_INET, SOCK_STREAM, 0);
if(hServSock == INVALID_SOCKET)
ErrorHandler("socket error");
memset(&servAddr, 0, sizeof(servAddr));
servAddr.sin_family = AF_INET;
servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
servAddr.sin_port = htons(atoi(argv[1]));
if(bind(hServSock, (SOCKADDR *)&servAddr, sizeof(servAddr)) == SOCKET_ERROR)
{
ErrorHandler("bind socket error");
}
if(listen(hServSock, 5) == SOCKET_ERROR)
ErrorHandler("listen socket error");
szClntAddr = sizeof(clntAddr);
hClntSock = accept(hServSock, (SOCKADDR *)&clntAddr, &szClntAddr);
if(hClntSock == INVALID_SOCKET)
ErrorHandler("accept error");
send(hClntSock, message, sizeof(message), 0);
closesocket(hClntSock);
closesocket(hServSock);
WSACleanup();
return 0;
}
void ErrorHandler(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
编译:菜单 Terminal > Run Build Task ,或者快捷键 Ctrl+Shift+B 。
运行:在终端
键入 .\hello_server_win.exe 1234
- 客户端
#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h>
void ErrorHandler(char *message);
int main(int argc, char *argv[])
{
WSADATA wsaData;
SOCKET hClntSock;
SOCKADDR_IN servAddr;
char message[30];
int strLen;
if(argc != 3)
{
printf ("Usage: %s <IP> <port>\n", argv[0]);
exit(1);
}
if(WSAStartup(MAKEWORD(2,2), &wsaData) != 0)
ErrorHandler("WSAStartup failed");
hClntSock = socket(PF_INET, SOCK_STREAM, 0);
if(hClntSock == INVALID_SOCKET)
ErrorHandler("socket error");
memset(&servAddr, 0, sizeof(servAddr));
servAddr.sin_family = AF_INET;
servAddr.sin_addr.s_addr = inet_addr(argv[1]);
servAddr.sin_port = htons(atoi(argv[2]));
if(connect(hClntSock, (SOCKADDR *)&servAddr, sizeof(servAddr)) == SOCKET_ERROR)
ErrorHandler("connect socket error");
strLen = recv(hClntSock, message, sizeof(message)-1, 0);
if(strLen == EOF)
ErrorHandler("read() error!");
printf("Message from server: %s \n", message);
closesocket(hClntSock);
WSACleanup();
return 0;
}
void ErrorHandler(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
编译:菜单 Terminal > Run Build Task ,或者快捷键 Ctrl+Shift+B 。
运行:在终端
键入 .\hello_client_win.exe 127.0.0.1 1234
更多推荐
所有评论(0)