偶然的机会,重新写了一下windows下socket通信的比较基础的代码,
太久没有接触socket以及多线程,查了不少博客,但是发现大部分内容比较陈旧,
所以决定写一篇博客,顺便自己总结一下。
内容简介
- 网络通信基本函数介绍
- C++11多线程简介
- socket通信tcp版本
- socket通信udp版本
网络通信基本函数介绍
tcp连接模式下
客户端流程
1.创建socket(套接字)
socket(int af, int type, int protocol)
第一个参数用来说明网络协议类型,tcp/ip协议只能用AF_INET
第二个参数:socket通讯类型(tcp,udp等)
第三个参数:与选择的通讯类型有关
2.向服务器发起连接
SOCKADDR_IN 声明套接字类型
sin_family 协议家族
sin_port 通信端口
sin_addr 网络ip
htons 将整型类型转换成网络字节序
htonl 将长整型转换成网络字节序
inet_pton(int af, char * str, pvoid addrbuf) 将点分十进制ip地址转换成网络字节
第一个参数:协议家族
第二个参数:点分十进制ip地址,例如:"127.0.0.1"
第三个参数:sin_addr
inte_ntop (int af, pvoid addrbuf, char *str, size_t len) 将网络字节序转换成点分十进制ip地址
第一个参数:协议家族
第二个参数:sin_addr
第三个参数:存储ip地址的字符串
第四个参数:字节单位长度
connect(socket s, const sockaddr *name, int namelen)
第一个参数:创建的套接字
第二个参数:要链接的套接字地址
第三个参数:单位长度
3.client与server通信
send(socket s, char * str, int len, int flag)
第一个参数:本机创建的套接字
第二个参数:要发送的字符串
第三个参数:发送字符串长度
第四个参数:会对函数行为产生影响,一般设置为0
recv(socket s, char * buf, int len,int flag)
第一个参数:本机创建的套接字
第二个参数:接受消息的字符串
第三个参数:允许接收字符串的最大长度
第四个参数:会对函数行为产生影响,一般设置为0
4.释放套接字
closesocket 释放套接字
服务端流程
1.创建套接字
2.将套接字与本地的ip和端口绑定
bind(socket s, const sockaddr *name, int namelen)
第一个参数:创建的套接字
第二个参数:要绑定的信息
第三个参数:套接字类型单位长度
3.设置为监听状态
listen(socket s, int num)
第一个参数:要监听的socket(套接字)
第二个参数:等待连接队列的最大长度
4.等待客户请求到来;当请求到来后,接受连接请求,返回一个新的对应于此次连接的套接字
accept(int socket, sockaddr *name, int *addrlen)
第一个参数,是一个已设为监听模式的socket的描述符。
第二个参数,是一个返回值,它指向一个struct sockaddr类型的结构体的变量,保存了发起连接的客户端得IP地址信息和端口信息。
第三个参数,也是一个返回值,指向整型的变量,保存了返回的地址信息的长度。
accept函数返回值是一个客户端和服务器连接的SOCKET类型的描述符,在服务器端标识着这个客户端。
5.相互通信
6.断开连接
udp通信
客户端
1.创建套接字
2.绑定服务器ip和port已经协议家族
3.相互通信
sendto(socket s, const char * buf, int len, int flags, const sockaddr *to,int tolen)
第一个参数:创建的套接字
第二个参数:要发送的字符串
第三个参数:要发送的字符串长度
第四个参数:影响函数的行为,一般为0
第五个参数:远端套接字信息
第六个参数:套接字单位长度
recvfrom(socket s, char * buf, int len, int flags, const sockaddr *to,int tolen)
第一个参数:创建的套接字
第二个参数:存储接收的字符串
第三个参数:可接受的最大字符串长度
第四个参数:影响函数的行为,一般为0
第五个参数:远端套接字信息
第六个参数:套接字单位长度
4.释放套接字
服务端
1.创建套接字
2.绑定本地的端口,ip,协议家族信息
3.通信
4.释放连接
C++11多线程简介
我们在socket通信过程中经常会有多个客户端向服务端发起请求的问题,在tcp模式下,会发生阻塞,解决的办法有accetp+多线程或者select+accept模式,这篇博客主要对前者就行详解,在查阅资料的过程中,我发现网上好多的博客介绍的多线程机制都是多年以前的方式,使用起来特别麻烦,比如pthread这种库,其实C++11以后,官方就已经将线程库封装了起来,我们只需要声明一下即可,该库thread
#include<thread>
定义 std::thread xxx;
构造 std::thread(func, ...)
等待线程结束 xxx.join()
socket通信tcp版本
服务器端
// ServerTcp.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#pragma comment(lib, "ws2_32.lib")
#include <Winsock2.h>
#include <WS2tcpip.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <thread>
void func(SOCKET arg, SOCKADDR_IN client_addr, int pos)
{
char recvBuf[128];
char sendBuf[128];
char tempBuf[256];
int sockConn = arg;
while (true)
{
// 从客户端接收消息
printf("wait receive client message :\n");
recv(sockConn, recvBuf, 128, 0);
// 解析客户端地址信息
char ipClient[16];
inet_ntop(AF_INET, &client_addr.sin_addr, ipClient, sizeof(ipClient));
printf("%s said: %s\n", ipClient, recvBuf);
// 向客户端发送消息
gets_s(sendBuf);
send(sockConn, sendBuf, strlen(sendBuf) + 1, 0);
}
}
int main()
{
/**********************************************************************/
/*
WSAStartup必须是应用程序或DLL调用的第一个Windows Sockets函数。
它允许应用程序或DLL指明Windows Sockets API的版本号及获得特定Windows Sockets实现的细节。
应用程序或DLL只能在一次成功的WSAStartup()调用之后才能调用进一步的Windows Sockets API函数。
*/
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(1, 1);
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0) { return 0; }
if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)
{
WSACleanup();
return 0;
}
/***********************************************************************/
// 申请存储线程的数组
std::thread threads[10];
int thread_num = 0;
/***********************************************************************/
/* socket通讯 */
// 申请套接字
SOCKET Svr = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
SOCKADDR_IN addr;
// 要绑定的基础信息
addr.sin_family = AF_INET;
addr.sin_port = htons(6002);
addr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
// 进行绑定
int len = sizeof(sockaddr);
bind(Svr, (struct sockaddr*)&addr, len);
// 监听套接字
int ret = listen(Svr, 10);
if (ret == SOCKET_ERROR)
{
printf("侦听失败\n");
closesocket(Svr);
}
// 存储请求连接的套接字信息
SOCKADDR_IN addrClient;
while (true)
{
// 接受连接,返回一个socket
SOCKET sockConn = accept(Svr, (struct sockaddr*)&addrClient, &len);
if (sockConn == INVALID_SOCKET)
{
//printf("无效socket\n");
continue;
}
// 将通讯细节放在线程里处理
threads[thread_num] = std::thread(func, sockConn, addrClient, thread_num);
thread_num++;
if (thread_num == 5)
{
printf("线程池达到数量上限");
}
}
// 等待线程结束
for (int i = 0; i < thread_num; i++)
threads[i].join();
// 释放套接字
closesocket(Svr);
WSACleanup();
return 0;
}
客户端
// ClientTcp.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#pragma comment(lib, "ws2_32.lib")
#include <Winsock2.h>
#include <WS2tcpip.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <thread>
void new_client(int pos)
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(1, 1);
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0) { return ; }
if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)
{
WSACleanup();
return ;
}
SOCKET Client = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
SOCKADDR_IN Server;
// 要连接的基础信息
Server.sin_family = AF_INET;
Server.sin_port = htons(6002);
inet_pton(AF_INET, "127.0.0.1", &Server.sin_addr); //点分十进制地址转换成网络字节序
// 向服务端发起连接
int ret = connect(Client, (struct sockaddr*)&Server, sizeof(Server));
if (ret == SOCKET_ERROR)
{
printf("连接失败\n");
closesocket(Client);
WSACleanup();
return;
}
char recvBuf[128];
char sendBuf[128];
while (true)
{
printf("please input message:\n");
gets_s(sendBuf);
sprintf_s(sendBuf, "%s_%d", sendBuf, pos);
send(Client, sendBuf, strlen(sendBuf) + 1, 0);
recv(Client, recvBuf, 128, 0);
char ipServer[16];
inet_ntop(AF_INET, &Server.sin_addr, ipServer, sizeof(ipServer));
printf("%s said: %s\n", ipServer, recvBuf);
}
closesocket(Client);
WSACleanup();
}
int main()
{
std::thread threads[10];
int client_num = 1;
for (int i = 0; i < client_num; i++)
threads[i] = std::thread(new_client, i);
for (int i = 0; i < client_num; i++)
threads[i].join();
return 0;
}
socket通信udp版本
服务端
// ServerUdp.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "stdafx.h"
#pragma comment(lib, "ws2_32.lib")
#include <Winsock2.h>
#include <WS2tcpip.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <thread>
void func(SOCKET arg, SOCKADDR_IN client_addr, int pos)
{
char recvBuf[128];
char sendBuf[128];
int sockConn = arg;
int len = sizeof(sockaddr);
while (true)
{
// 从客户端接收消息
printf("wait receive client message :\n");
recvfrom(sockConn, recvBuf, 128, 0, (struct sockaddr*)&arg, &len);
// 解析客户端地址信息
char ipClient[16];
inet_ntop(AF_INET, &client_addr.sin_addr, ipClient, sizeof(ipClient));
printf("%s said: %s\n", ipClient, recvBuf);
// 向客户端发送消息
gets_s(sendBuf);
sendto(sockConn, sendBuf, strlen(sendBuf) + 1, 0, (struct sockaddr*)&arg, len);
}
}
int main()
{
/**********************************************************************/
/*
WSAStartup必须是应用程序或DLL调用的第一个Windows Sockets函数。
它允许应用程序或DLL指明Windows Sockets API的版本号及获得特定Windows Sockets实现的细节。
应用程序或DLL只能在一次成功的WSAStartup()调用之后才能调用进一步的Windows Sockets API函数。
*/
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(1, 1);
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0) { return 0; }
if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)
{
WSACleanup();
return 0;
}
/***********************************************************************/
// 申请存储线程的数组
std::thread threads[10];
int thread_num = 0;
/***********************************************************************/
/* socket通讯 */
// 申请套接字
SOCKET Svr = socket(AF_INET, SOCK_DGRAM, 0);
SOCKADDR_IN addr;
// 要绑定的基础信息
addr.sin_family = AF_INET;
addr.sin_port = htons(6002);
addr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
// 进行绑定
int len = sizeof(sockaddr);
bind(Svr, (struct sockaddr*)&addr, len);
// 存储请求连接的套接字信息
SOCKADDR_IN addrClient;
// 将通讯细节放在线程里处理
threads[thread_num] = std::thread(func, Svr, addrClient, thread_num);
thread_num++;
// 等待线程结束
for (int i = 0; i < thread_num; i++)
threads[i].join();
// 释放套接字
closesocket(Svr);
WSACleanup();
return 0;
}
客户端
// ClientUdp.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#pragma comment(lib, "ws2_32.lib")
#include <Winsock2.h>
#include <WS2tcpip.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <thread>
void new_client(int pos)
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(1, 1);
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0) { return; }
if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)
{
WSACleanup();
return;
}
SOCKET Client = socket(AF_INET, SOCK_DGRAM, 0);
SOCKADDR_IN Server;
// 要连接的基础信息
Server.sin_family = AF_INET;
Server.sin_port = htons(6002);
inet_pton(AF_INET, "127.0.0.1", &Server.sin_addr); //点分十进制地址转换成网络字节序
char recvBuf[128];
char sendBuf[128];
int len = sizeof(sockaddr);
while (true)
{
printf("please input message:\n");
gets_s(sendBuf);
sprintf_s(sendBuf, "%s_%d", sendBuf, pos);
sendto(Client, sendBuf, strlen(sendBuf) + 1, 0, (struct sockaddr*)&Server, len);
recvfrom(Client, recvBuf, 128, 0, (struct sockaddr*)&Server, &len);
char ipServer[16];
inet_ntop(AF_INET, &Server.sin_addr, ipServer, sizeof(ipServer));
printf("%s said: %s\n", ipServer, recvBuf);
}
closesocket(Client);
WSACleanup();
}
int main()
{
std::thread threads[10];
int client_num = 1;
for (int i = 0; i < client_num; i++)
threads[i] = std::thread(new_client, i);
for (int i = 0; i < client_num; i++)
threads[i].join();
return 0;
}