【并发编程七】进程通信——套接字(socket)_80行代码实现一个聊天软件
一、简介
套接字是什么?基于上面两篇文章,关于socket简单说两句。
- 四元组
- 近于应用层和传输控制层。
- 通过系统调用,返回内核的文件描述符。
- 阻塞和非阻塞在于,阻塞会在没有消息时会等待,非阻塞在没有消息时会返回一个错误,让程序继续向后运行。
二、相关知识介绍
1、winsock1.h、winsock2.h
WinSock(Windows Socket)编程依赖于系统提供的动态链接库(DLL),有两个版本:
- 较早的DLL是 wsock32.dll,对应的头文件为 winsock1.h;
- 最新的DLL是 ws2_32.dll,对应的头文件为 winsock2.h。
2、如何使用ws2_32.dll
使用 DLL 之前必须把 DLL 链接到当前程序,你可以在编译时链接,也可以在程序运行时链接,我们已在cmake系列《【cmake实战六】如何使用编译的库(动态库dll)——windows系统》、《【cmake实战七】如何使用编译的库(动态库dll)2——windows系统》进行了讲解。
这里使用#pragma命令,在编译时加载:
#pragma comment (lib, "ws2_32.lib")
target_link_libraries(Client "Ws2_32")
备注:本文使用的是编译时链接。
3、WSAStartup() 函数
使用 DLL 之前,还需要调用 WSAStartup() 函数进行初始化,以指明 WinSock 规范的版本,它的原型为:
- parm1:请求的socket版本 2.2、2.1、2.0 ;parm2:传出的参数
int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);
4、socket
socket:创建套接字
- parm1: af 地址协议族 ipv4 ipv6
- parm2:type 传输协议类型 流式套接字(SOCK_STREAM),数据包套接字(SOCK_DGRAM)
- parm3:ptotoc1 使用具体的某个传输协议
SOCKET WSAAPI socket(
[in] int af,
[in] int type,
[in] int protocol
);
代码中我们使用的是ipv4,流式、TCP。
5、bind
- 绑定ip端口号,绑定函数将本地地址与套接字相关联。
int WSAAPI bind(
[in] SOCKET s,
[in] const sockaddr *name,
[in] int namelen
);
5、listen
int WSAAPI listen(
[in] SOCKET s,
[in] int backlog
);
6、accept
- accept 函数允许在套接字上尝试传入连接。
- 等待客户都链接
SOCKET WSAAPI accept(
[in] SOCKET s,
[out] sockaddr *addr,
[in, out] int *addrlen
);
7、connect
- connect 函数建立与指定套接字的连接。
- 客户端链接服务端。
int WSAAPI connect(
[in] SOCKET s,
[in] const sockaddr *name,
[in] int namelen
);
详细可以参考微软的官方文档winsock2.h 标头
三、聊天软件的代码如下
客户端和服务端分属于两个进程。(当然,本代码只是仅仅实现了socket客户端和服务端的聊天通信,并不设计到用户信息的注册、多客户端链接等。)
1、服务端
- 过程
- 初始化
- 创建socket
- 绑定端口号和IP
- 监听端口
- 接收服务端的链接
- 接收数据
- main.cpp
#include <winsock2.h>
#include<windows.h>
#include <iostream>
using namespace std;
int main()
{
int errCode = 0;
cout << "==============socket server begin start.=============="<<endl;
{
cout << "begin init socket." << endl;
WSADATA wsadata;
errCode = WSAStartup(MAKEWORD(2, 2), &wsadata);
if(0 != errCode)
{
cout << "init socket version faile" << endl;
return -1;
}
cout << "init socket version sucess" << endl;
}
SOCKET fd;
{
fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (INVALID_SOCKET == fd)
{
cout << "create socket faile,get a invalid socket fd." << endl;
}
cout << "create socket sucess,get a valid socket fd." << endl;
SOCKADDR_IN addr;
addr.sin_family = AF_INET;
addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
addr.sin_port = htons(8888);
errCode = bind(fd, (SOCKADDR*)&addr, sizeof(SOCKADDR));
if (SOCKET_ERROR == errCode)
{
cout << "bind ip port faile" << endl;
}
cout << "bind ip port sucess" << endl;
listen(fd, 5);
cout << "create socket sucess!" << endl << "begin listen..." << endl << endl;
}
SOCKET fd_server;
{
fd_server = accept(fd, NULL, NULL);
if (INVALID_SOCKET == fd_server)
{
cout << "fd_server is invalid." << endl;
}
cout << "fd_server is valid." << endl<<endl;
}
cout << "==============begin talking.server ==============" << endl;
while (1)
{
char receiveBuf[1024] = { 0 };
errCode = recv(fd_server, receiveBuf, 1024, 0);
if (errCode <= 0)
{
cout << "receive data faile" << endl;
}
cout << "receive>: "<<receiveBuf<<endl;
char sendBuf[1024] = { 0 };
cout << "send>: ";
cin.getline(sendBuf, 1024);
send(fd_server, sendBuf, 1024, 0);
}
closesocket(fd_server);
closesocket(fd);
WSACleanup();
return 0;
}
CMAKE_MINIMUM_REQUIRED(VERSION 3.8.0)
PROJECT(qq)
ADD_EXECUTABLE(Server main.cpp)
target_link_libraries(Server "Ws2_32")
ADD_SUBDIRECTORY(Client)
SET(EXECUTABLE_OUTPUT_PATH "${PROJECT_SOURCE_DIR}/lib")
2、客户端
#include <winsock2.h>
#include<windows.h>
#include <iostream>
using namespace std;
int main()
{
int errCode = 0;
cout << "==============socket client begin start.==============" << endl;
{
cout << "begin init socket." << endl;
WSADATA wsadata;
errCode = WSAStartup(MAKEWORD(2, 2), &wsadata);
if (0 != errCode)
{
cout << "init socket version faile" << endl;
return -1;
}
cout << "init socket version sucess" << endl;
}
SOCKET fd;
{
fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (INVALID_SOCKET == fd)
{
cout << "create socket faile,get a invalid socket fd." << endl;
}
cout << "create socket sucess,get a valid socket fd." << endl;
SOCKADDR_IN addr;
addr.sin_family = AF_INET;
addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
addr.sin_port = htons(8888);
errCode = connect(fd, (SOCKADDR*)&addr, sizeof(SOCKADDR));
if (SOCKET_ERROR == errCode)
{
cout << "connect faile" << endl;
return -1;
}
cout << "connect sucess" << endl<<endl;
}
cout << "==============begin talking.client==============" << endl;
while (1)
{
cout << "send>: ";
char sendBuf[1024] = {0};
cin.getline(sendBuf,1024);
if (SOCKET_ERROR == send(fd, sendBuf, 1024, 0))
{
cout << "send data error" << endl;
return -1;
}
char receiveBuf[1024];
errCode = recv(fd, receiveBuf, 1024, 0);
if (errCode <= 0)
{
cout << "receive data faile" << endl;
}
cout << "receive>" << receiveBuf << endl;
}
closesocket(fd);
WSACleanup();
return 0;
}
CMAKE_MINIMUM_REQUIRED(VERSION 3.8.0)
SET(TARGET "Client")
ADD_EXECUTABLE(Client main.cpp)
target_link_libraries(Client "Ws2_32")
SET(LIBRARY_OUTPUT_PATH "${PROJECT_SOURCE_DIR}/lib")
SET(EXECUTABLE_OUTPUT_PATH "${PROJECT_SOURCE_DIR}/lib")
四、cmake构建、编译、运行
文件目录如下
1、构建
cmake -B build
2、编译 当然,你也可以使用vs手动编译
cmake --build build
3、生成的项目组下图
五、输出
六、c++网络通信的库
- 1、c++用途这么广泛的语言,竟然没有一个标准的c++库。
- 2、之前在某位大佬的文章看到,说是c++23或者c++26,可能会把网络通信引入c++标准库。
- 3、除了本文说的调用系统函数,还可以使用第三方库来实现网络通信。
- 4、第三方网络库对各个系统的兼容性、和性能未知,所以如果要做跨平台开发的化,可以再多做些调研。
|