Socket网络编程实现过程简单总结
一、 服务器端
1、 加载及释放套接字库
a) 使用函数WSAStartup()绑定相应的套接字库:
当一个应用程序调用WSAStartup函数时,操作系统根据请求的Socket版本来搜索相应的Socket库,然后绑定找到的Socket库到该应用程序中。以后应用程序就可以调用所请求的Socket库中的其它Socket函数了。该函数执行成功后返回0。
Ex:
wVersionRequested = MAKEWORD( 2, 1 );//指定加载2.1版本
err =WSAStartup( wVersionRequested, &wsaData );
关于Socket版本:不同版本是有区别的,例如1.1版只支持TCP/IP协议,而2.0版可以支持多协议。2.0版有良好的向后兼容性,任何使用1.1版的源代码、二进制文件、应用程序都可以不修改地在2.0规范下使用。此外winsock 2.0支持异步 1.1不支持异步.
b) 使用函数WSACleanup ()绑定相应的套接字库:
应用程序在完成对请求的Socket库的使用后,要调用WSACleanup函数来解除与Socket库的绑定并且释放Socket库所占用的系统资源。
c) 以上二者需要库Ws2_32.lib和头文件winsock2.h
#include"winsock2.h"
#pragma comment(lib,”ws2_32.lib”)
2、 创建套接字
socket(domain=AF_INET,type=SOCK_STREAM,proto=IPPROTO_TCP)
三个参数分别是:地址系列,套接字类型,协议号
1)、Domain : Domain参数指定了通信的”域”
AF_UNIX :AF_LOCAL本地通信
AF_INET:IPv4网络通信
AF_INET6:IPv6网络通信
AF_PACKET:链路层通信
2)、Type: Type就是socket的类型,对于AF_INET协议族而言有流套接字(SOCK_STREAM)、数据包套接字(SOCK_DGRAM)、原始套接字(SOCK_RAW)。
SOCK_STREAM:流套接字, 提供顺序,可靠,双向,基于连接的字节流。 可以支持带外数据传输机制。例
如:TCP协议、FTP协议
SOCK_DGRAM:数据包套接字, 支持数据报(无连接,不可靠的固定最大长度的消息)例如:UDP协议
SOCK_RAW:原始套接字, 使用原始套接字时候调用,原始套接字也就是链路层协议
SOCK_SEQPACKET:有序分组套接字,为固定最大长度的数据报提供有序,可靠,双向连接的数据传输路径; 消费者需要利用每个输入系统调用读取整个分组
3)、Protocol:支持的协议
参数解析参考:https://blog.csdn.net/liuxingen/article/details/44995467
3、 绑定服务器套接字地址
int bind(intsockfd, const struct sockaddr *addr,socklen_t addrlen);
服务端套接字绑定自己的IP地址与端口号,客户端那边可以不写,内核会给它分配一个临时的端口。
参数:
1)、sockfd: 服务器或者客户端自己创建的socket
2)、sockaddr: 服务器或者客户端自己的地址信息(协议族、IP、端口号)
sockaddr 与sockaddr_in:
二者的占用的内存大小是一致的,因此可以互相转化,从这个意义上说,他们并无区别。
sockaddr常用于bind、connect、recvfrom、sendto等函数的参数,指明地址信息。是一种通用的套接字地址。而sockaddr_in 是internet环境下套接字的地址形式。所以在网络编程中我们会对sockaddr_in结构体进行操作。使用sockaddr_in来建立所需的信息,最后使用类型转化就可以了。
3)、socklen_t: 服务器或者客户端自己的地址信息的长度
EXP:
seraddr.sin_family = AF_INET; // 设置地址族为IPv4
seraddr.sin_port = htons(SERPORT); //设置地址的端口号信息
seraddr.sin_addr.s_addr = inet_addr(SERADDR); // 设置IP地址
ret= bind(sockfd, (const struct sockaddr *)&seraddr, sizeof(seraddr));
参考:https://blog.csdn.net/zz709196484/article/details/54864770
4、 将套接字设置为监听模式等待连接请求
intlisten(int sockfd, int backlog);
用法:函数应该在调用socket和bind这两个函数之后,accept函数之前调用。
作用:让服务器套接字sockfd进入监听状态。
参数:sockfd:套接字,成功返回后进入监听模式,当有新连接并accept后会再建立一个套接字保存新的连接;
backlog:并发连接数,下面详细介绍此参数:
1) 当TCP接收一个连接后(三次握手通过)会将此连接存在连接请求队列里面,并对队列个数+1,而backlog为此队列允许的最大个数,超过此值,则直接将新的连接删除,即不在接收新的连接。将这些处于请求队列里面的连接暂记为后备连接,这些都在底层自动完成,底层将连接添加到队列后等待上层来处理(一般是调用accept函数接收连接);
2) 当上层调用accept函数接收一个连接(处于请求队列里面的后备连接),队列个数会-1;
3) 那么这样一个加一个减,只要底层提交的速度小于上层接收的速度(一般是这样),很明显backlog就不能限制连接的个数,只能限制后备连接的个数。那为啥要用这个backlog呢?主要用于并发处理,当上层没来的及接收时,底层可以提交多个连接;
4) backlog的取值范围 ,一般为0-5。
5、 接受连接请求,返回一个新的对应于此次连接的套接字
accept(intsocket, sockaddr *name, int *addrlen)
参数socket: 是一个已设为监听模式的服务器端socket的描述符。
参数sockaddr: 是一个返回值,它指向一个struct sockaddr类型的结构体的变量,保存了发起连接的客户端得IP地址信息和端口信息。
参数addrlen: 也是一个返回值,指向整型的变量,保存了返回的地址信息的长度。
accept函数返回值是一个客户端和服务器连接的SOCKET类型的描述符,在服务器端标识着这个客户端。
6、 用5返回的套接字和客户端进行通信(send()/recv());
send(sockets, char * str, int len, int flag)
第一个参数:本机创建的套接字
第二个参数:要发送的字符串
第三个参数:发送字符串长度
第四个参数:会对函数行为产生影响,一般设置为0
recv(socket s, char * buf, int len,intflag)
参数socket:创建的可以传输消息过来的套接字
参数buf:接受消息的字符串缓存
参数len:允许接收字符串的缓存的最大长度
第四个参数:会对函数行为产生影响,一般设置为0
send和recv实际上分别是write和read函数的基础上扩展了第四个参数:
1)、recv对应的flags有3个选项:
MSG_PEEK:查看数据,并不从系统缓冲区移走数据
MSG_WAITALL:等待所有数据,等到所有的信息到达时才返回,使用它时,recv返回一直阻塞,直到指定的条件满足时,或者发生错误
MSG_OOB:接受或者发送带外数据
2)、send第四个参数flags,有2个选项:
MSG_DONTROUTE:不查找表,它告诉ip,目的主机在本地网络上,没必要查找表。(一般用在网络诊断和路由程序里面)
MSG_OOB:接受或者发送带外数据
功能上是分别将SendBuff拷贝到发送缓冲区中和将接受缓冲区的数据拷贝到ReciveBuf中;
https://blog.csdn.net/tingyun_say/article/details/51907687
https://blog.csdn.net/rankun1/article/details/50488989
二、 客户端
1、加载套接字库,创建套接字(WSAStartup()/socket());
2、向服务器发出连接请求(connect());
3、和服务器进行通信(send()/recv());
4、关闭套接字,关闭加载的套接字库(closesocket()/WSACleanup());
三、 代码实现
环境:window7、vs2015
服务器端:
#include<iostream>
#include "winsock2.h"
#pragma comment(lib,"ws2_32.lib")
using namespace std;
int main()
{
int RetVal;
WORD SocketVersion=MAKEWORD(2, 2);
WSADATA wsd;
if (WSAStartup(SocketVersion, &wsd) != 0)
{
cout << "绑定Socket库失败" << endl;
}
SOCKET ServerSocket;
ServerSocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if (ServerSocket == INVALID_SOCKET)
{
cout << "创建服务器套接字失败" << endl;
WSACleanup();
return -1;
}
SOCKADDR_IN ServerAddr;
ServerAddr.sin_family = AF_INET;
ServerAddr.sin_port = htons(2345);
ServerAddr.sin_addr.S_un.S_addr = INADDR_ANY;
RetVal = bind(ServerSocket, (SOCKADDR *)&ServerAddr, sizeof(SOCKADDR_IN));
if (RetVal == SOCKET_ERROR)
{
cout << "套接字绑定失败" << endl;
closesocket(ServerSocket);
WSACleanup();
return -1;
}
RetVal = listen(ServerSocket,2);
if (RetVal == SOCKET_ERROR)
{
cout << "套接字监听失败" << endl;
closesocket(ServerSocket);
WSACleanup();
return -1;
}
SOCKET ClientSocket;
SOCKADDR_IN ClientAddr;
int ClientAddrLen = sizeof(ClientAddr);
ClientSocket = accept(ServerSocket, (SOCKADDR*)&ClientAddr, &ClientAddrLen);
if (ClientSocket == INVALID_SOCKET)
{
cout << "接收客户端请求失败" << endl;
closesocket(ServerSocket);
WSACleanup();
return -1;
}
char ReceiveBuff[BUFSIZ];
char SendBuff[BUFSIZ];
while (true)
{
ZeroMemory(ReceiveBuff, BUFSIZ);
RetVal = recv(ClientSocket, ReceiveBuff, BUFSIZ, 0);
if (RetVal == SOCKET_ERROR)
{
cout << "接收数据失败" << endl;
closesocket(ServerSocket);
closesocket(ClientSocket);
WSACleanup();
return -1;
}
cout << "接收自客户端数据:" << ReceiveBuff << endl;
cout << "向客户端发送数据:";
cin >> SendBuff;
send(ClientSocket, SendBuff, strlen(SendBuff), 0);
}
closesocket(ServerSocket);
closesocket(ClientSocket);
WSACleanup();
return 0;
}
客户端:
#include<iostream>
#include "winsock2.h"
#pragma comment(lib,"ws2_32.lib")
using namespace std;
int main()
{
const int BUF_SIZE = 64;
int RetVal;
WSADATA Wsd;
if (WSAStartup(MAKEWORD(2, 2), &Wsd) != 0)
{
cout << "初始化套接字动态库失败" << endl;
return -1;
}
SOCKET ServerScoket;
ServerScoket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if (ServerScoket == INVALID_SOCKET)
{
cout << "创建套接字失败" << endl;
WSACleanup();
return -1;
}
SOCKADDR_IN ServerAddr;
ServerAddr.sin_family = AF_INET;
ServerAddr.sin_port = htons(2345);
ServerAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
RetVal = connect(ServerScoket, (SOCKADDR*)&ServerAddr, sizeof(ServerAddr));
if (RetVal == SOCKET_ERROR)
{
cout << "链接服务器失败" << endl;
closesocket(ServerScoket);
WSACleanup();
return -1;
}
char SendBuff[BUF_SIZE];
char RECVBuff[BUF_SIZE];
while (true)
{
ZeroMemory(SendBuff, BUF_SIZE);
cout << "向服务器发送数据" << endl;
cin >> SendBuff;
RetVal = send(ServerScoket, SendBuff, strlen(SendBuff),0);
if (RetVal == SOCKET_ERROR)
{
cout << "发送数据失败" << endl;
closesocket(ServerScoket);
WSACleanup();
return -1;
}
ZeroMemory(RECVBuff, BUF_SIZE);
recv(ServerScoket, RECVBuff, BUF_SIZE, 0);
cout << endl << "从服务器接收数据:" << RECVBuff << endl;
}
closesocket(ServerScoket);
WSACleanup();
}