套接字
什么是套接字?套接字(socket)是操作系统提供的用来进行网络数据传输的软件设备。即使对网路数据传输原理不太熟悉,也可以通过套接字完成数据传输。
操作系统提供相应的函数,windows和linux提供的函数有一些差异,但大致思想是一样的。下面介绍windows的几个函数:
socket函数创建套接字:
SOCKET socket(int af, int type, int protocol);
成功时返回套接字句柄,失败时返回INVALID_SOCKET。句柄其实就是标识符,唯一地标识某个东西。
bind函数为套接字分配IP地址和端口号:
int bind(SOCKET s, const struct sockaddr *name, int namelen);
成功时返回0,失败时返回SOCKET_ERROR。
listen函数使套接字可接受客户端连接,即将套接字转换成可接收连接的状态:
int listen(SOCKET s, int backlog);
成功时返回0,失败时返回SOCKET_ERROR。
accept函数使套接字处理客户端连接请求:
SOCKET accept(SOCKET s, struct sockaddr *addr, int *addrlen);
成功时返回套接字句柄,失败时返回INVALID_SOCKET。
connect函数从客户端发送请求:
int connect(SOCKET s, const struct sockaddr *name, int namelen);
成功时返回0,失败时返回SOCKET_ERROR。
send函数:
int send(SOCKET s, const char *buf, int len, int flags);
成功时返回传输字节数,失败时返回SOCKET_ERROR。s表示数据传输对象连接的套接字句柄值,buf保存待传输数据的缓冲地址值,len表示要传输的字节数,flags为传输数据时用到的多种选项信息。
recv函数:
int recv(SOCKET s, const char *buf, int len, int flags);
成功时返回接收到的字节数,失败时返回SOCKET_ERROR。参数表示含义和send类似。
服务端的一般步骤是:
(1)调用socket函数创建套接字。
(2)调用bind函数分配IP地址和端口号。
(3)调用listen函数转换为可接收请求状态。
(4)调用accept函数受理连接请求。
客户端的一般步骤是:
(1)调用socket函数创建套接字。
(2)调用connect函数向服务端发送连接请求。
环境配置
windows下套接字编程需要用到头文件WinSock2.h,以及链接ws2_32.lib库。
vs中链接ws2_32.lib库的方法:进入项目属性--》输入--》附加依赖项 直接写入ws2_32.lib。
windows下套接字编程
Winsock其实就是Windows Socket的简称,即Windows套接字。
进行Winsock编程时,首先必须调用WSAStartup函数,设置程序中用到的Winsock版本,并初始化相应版本的库。
int WSAStartup(WORD mVersionRequested, LPWSADATA lpWSAData);
Winsock存在多个版本,应准备WORD类型的套接字版本信息,传递给函数的第一个参数。WORD是通过typedef声明定义的unsigned short类型。可以借助MAKEWORD宏函数构建WORD型的版本信息,如MAKEWORD(1,2)返回的是0x0201,表示版本号1.2,MAKEWORD(2,2)返回的是0x0202,表示版本号2.2。
第二个参数传入WSADATA型结构体变量地址(LPWSADATA是WSADATA的指针类型)。调用完函数后,相应参数中将填充已初始化的库信息。
现在给一个小例子,服务段向客户端返回Hello World!。
服务器端代码:
#include<iostream>
#include<cstdio>
#include<WinSock2.h>
using namespace std;
void errorHandling(char *message);
int main()
{
WSADATA wsaData;
SOCKET serverSock, clientSock;
SOCKADDR_IN serverAddr, clientAddr;
int szClientAddr, port;
char message[] = "Hello World!";
cout << "输入端口号:";
cin >> port;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
errorHandling("WSAStartup() error");
serverSock = socket(PF_INET, SOCK_STREAM, 0);
if (serverSock == INVALID_SOCKET)
errorHandling("socket() error");
memset(&serverAddr, 0, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
serverAddr.sin_port = htons(port);
if (bind(serverSock, (SOCKADDR *)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR)
errorHandling("bind() error");
if (listen(serverSock, 5) == SOCKET_ERROR)
errorHandling("listen() error");
szClientAddr = sizeof(clientAddr);
clientSock = accept(serverSock, (SOCKADDR *)&clientAddr, &szClientAddr);
if (clientSock == INVALID_SOCKET)
errorHandling("accept() error");
send(clientSock, message, sizeof(message), 0);
closesocket(clientSock);
closesocket(serverSock);
WSACleanup();
return 0;
}
void errorHandling(char *message)
{
cerr << message << endl;
exit(1);
}
客户端代码:
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<WinSock2.h>
using namespace std;
void errorHandling(char *message);
int main()
{
WSADATA wsaData;
SOCKET mySocket;
SOCKADDR_IN serverAddr;
char message[30], ip[20];
int strlen, port;
cout << "输入ip:";
cin >> ip;
cout << "输入端口号:";
cin >> port;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
errorHandling("WSAStartup() error");
mySocket = socket(PF_INET, SOCK_STREAM, 0);
if (mySocket == INVALID_SOCKET)
errorHandling("socket() error");
memset(&serverAddr, 0, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = inet_addr(ip);
serverAddr.sin_port = htons(port);
if (connect(mySocket, (SOCKADDR *)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR)
errorHandling("connect() error");
strlen = recv(mySocket, message, sizeof(message) - 1, 0);
if (strlen == -1) errorHandling("read() error");
printf("Message from server:%s\n", message);
closesocket(mySocket);
WSACleanup();
return 0;
}
void errorHandling(char *message)
{
cerr << message << endl;
exit(1);
}
先运行服务端程序:
再运行客户端程序:
显然,端口号必须相同。IP地址是服务器端所在计算机的IP地址,如果服务器端和客户端是在同一台机器上,则可用127.0.0.1,127.0.0.1是回送地址,指的就是本机。