WinSock
在windows系统下有一个api专门提供socket编程,这个api即为WinSock,其中特别需要注意的是先需要调用WSAStartup函数初始化Windows Sockets API,结束后需要调用WSACleanup函数释放掉Windows Sockets DLL
socket编程
关于socket在客户端、服务端之间的通信可如图所示:
其中socket()函数是创建套接字函数,其函数如下:socket(int af,int type,int protocol) 其中af代表地址族,其中AF_INET代表TCP协议,type代表套接字的类型,套接字一般分为三种类型,即流式套接字、数据报套接字、原始套接字,protocol代表协议。
bind()是绑定套接字函数,其函数原型如下:bind(SOCKET s,const sockaddr*addr,int namelen),其中s代表套接字,addr代表一个数据结构sockaddr_in,其结构如下:
struct sockaddr_in{
u_char sin_len;//地址长度
u_char sin_family;//地址族(tcp/ip:AF_INET)
u_short sin_port;//端口号
struct in_addr sin_addr;//ip地址
char sin_zero[8];//未用,置零
}
一般而言只需服务端绑定即可。对于服务端绑定来说,由于服务端可以有多个网卡,可能有多个ip地址,其形式如图所示:
对于此可以采用地址通配符ANADDR_ANY,这样无论通过哪个地址都可以接受请求。
listen()函数是监听函数,其函数原型如下:listen(SOCKET S,int backlog) 其中s代表套接字,backlog代表监听队列的长度,一般而言监听队列的最大长度为20
accpet()函数是接受函数,当监听到连接请求之后,accept函数会返回一个新的套接字与客户端建立连接,而原来的套接字则继续等待连接请求。其函数原型如下:accept(SOCKET s,sockaddr*addr,int addrlen)
connect()函数是连接函数,是客户端与服务端的连接函数,其函数原型如下:connect(SOCKET S,const sockaddr*name,int namelen),其中name是指定的服务端的地址信息。
send()函数是发送数据函数,其函数原型如下:send(SOCKET*s,const char * buf,int len,int flags),其中flags未用,默认为0。
recv函数是接收数据函数,其函数原型如下:recv(SOCKET s,char*buf,int len,int flags)
closesocket()函数是关闭套接字函数,其函数原型如下:closesocket(SOCKET S)
其通信流程如下:首先服务端先创建套接字,然后绑定套接字,然后调用监听函数,其监听函数会把客户端的连接请求形成一个队列,然后调用accept()函数接受请求,创建一个新的套接字,让这个套接字与客户端套接字建立连接。之后调用recv()函数和send()函数实现客户端和服务端的数据接收和发送。在完成后,会调用closesocket()函数关闭套接字,这时服务端会返回到accept()函数中去继续接受请求。
代码
代码如下:
服务端:
#include<stdio.h>
#include<iostream>
#include<string>
#include<windows.h>
using namespace std;
#pragma comment(lib,"ws2_32.lib")
int main(int argc, char* argv[]) {
//初始化版本号
WORD socketVersion = MAKEWORD(2, 2);
WSADATA wsdata;
if (WSAStartup(socketVersion, &wsdata) != 0) {
return 1;
}
//创建套接字
SOCKET serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (serverSocket == INVALID_SOCKET) {
cout << "无效的套接字" << endl;
return 1;
}
//绑定套接字
sockaddr_in addr_in;
addr_in.sin_family = AF_INET;
addr_in.sin_port = htons(8888);
addr_in.sin_addr.S_un.S_addr = INADDR_ANY;//地址通配符
if (bind(serverSocket, (sockaddr*)&addr_in, sizeof(addr_in)) == SOCKET_ERROR) {
cout << "绑定失败" << endl;
return 1;
}
//监听套接字
if (listen(serverSocket, 10) == SOCKET_ERROR) {
cout << "监听失败" << endl;
return 1;
}
SOCKET connection_socket;//调用accept()函数时产生的新的套接字
sockaddr_in connection_addr_in;//新的套接字对应的地址
char data[1000];//存储要转发的数据
int flag = 0;//标志是否被连接
int len = sizeof(connection_addr_in);
while (true) {
if (!flag) {
cout << "正在连接中..." << endl;
}
connection_socket = accept(serverSocket, (sockaddr*)&connection_addr_in, &len);
if (connection_socket == INVALID_SOCKET) {
flag = 0;
cout << "接受失败" << endl;
return 1;
}
if (!flag) {
cout << "收到一个连接:" << inet_ntoa(connection_addr_in.sin_addr) << endl;
flag = 1;
}
int num = recv(connection_socket, data,100,0);
if (num > 0) {
data[num] = '\0';
cout << "服务端收到:" << data << endl;
}
string data;
getline(cin, data);
const char* senddata;
senddata = data.c_str();
send(connection_socket, senddata, strlen(senddata), 0);
closesocket(connection_socket);
}
closesocket(serverSocket);
WSACleanup();//关闭
return 0;
}
客户端代码:
#include<iostream>
#include<winsock2.h>
#include<string>
#include<Ws2tcpip.h>
#include<windows.h>
using namespace std;
#pragma comment(lib,"ws2_32.lib")
int main() {
WORD sockVersion = MAKEWORD(2, 2);
WSADATA data;
if (WSAStartup(sockVersion, &data) != 0) {
return 1;
}
while (true) {
SOCKET clientsocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);//地址族,流式套接字,tcp协议
if (clientsocket == INVALID_SOCKET) {
cout << "Socket error" << endl;
return 1;
}
sockaddr_in sock_in;//结构体,内包含地址族,端口,ip地址
sock_in.sin_family = AF_INET;
sock_in.sin_port = htons(8888);//htons函数是实现将端口号的顺序弄成与本地的一样,由于有大小端之分
inet_pton(AF_INET, "192.168.200.113", &sock_in.sin_addr.S_un.S_addr);//将ip地址转化成二进制模式传给sock_in中的ip地址,传输的时候是以二进制传输的
if (connect(clientsocket, (sockaddr*)&sock_in, sizeof(sock_in)) == SOCKET_ERROR) {
cout << "连接失败!" << endl;
return 1;
}
string data;
const char *senddata;
getline(cin, data);
senddata = data.c_str();
send(clientsocket, senddata, strlen(senddata), 0);//将数据发送给套接字
char revdata[1000];
int num = 0;
num = recv(clientsocket, revdata, strlen(revdata), 0);//接收发过来的信息
if (num > 0) {
revdata[num] = '\0';
cout << "客户端收到:" <<revdata << endl;
}
closesocket(clientsocket);//将套接字关闭
}
WSACleanup();//关闭
}
其中关于htons()函数主要是将本地字节顺序转换成网络字节顺序(两字节)由于机器的字节顺序有大端顺序和小端顺序,相应的也有ntohs()函数将网络字节顺序转化成本地字节顺序。
其结果如下:
(先启动服务端再启动客户端)
以上
参考:哈工大《计算机网络》,博客链接:c++socket编程