首页 > 系统相关 >【并发编程七】C++进程通信——套接字(socket)_80行代码实现一个聊天软件

【并发编程七】C++进程通信——套接字(socket)_80行代码实现一个聊天软件

时间:2023-03-16 16:22:31浏览次数:50  
标签:socket int 32 C++ 接字 链接 服务端

 

【并发编程七】进程通信——套接字(socket)_80行代码实现一个聊天软件

 

  • 前言:
    80行代码实现一个聊天软件。
    刚写完《【操作系统二】图解TCP/IP模型+实战》、和《【操作系统三】图解网络IO(bio\nio\slect\epoll)》,和网络通信相关的基本都介绍清楚了,所以原本这篇socket通信不想再写了,但是考虑到【并发编程】系列不完整,所以才写了下了这篇。所以本篇文章侧重Windows系统下socket的代码实战(写了一个简单的聊天软件),如果对什么是socket还没有清晰的印象,建议先读上面的两篇文章,然后再看本篇。

一、简介

套接字是什么?基于上面两篇文章,关于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>
//#pragma comment (lib,"ws2_32.lib") 因为cmake里面我们使用了target_link_libraries(Server "Ws2_32"),所以,在次我们不需要使用静态链接了。否则你需要把这行放开
using namespace std;
int main()
{
	int errCode = 0;
	cout << "==============socket server begin start.=============="<<endl;
	
	{
		// step1:初始化套接字版本
		cout << "begin init socket." << endl;
		WSADATA wsadata;//wsa 即windows socket async 异步套接字
		errCode = WSAStartup(MAKEWORD(2, 2), &wsadata);   // parm1:请求的socket版本 2.2、2.1、2.0 ;parm2:传出的参数
		if(0 != errCode)
		{
			cout << "init socket version faile" << endl;
			return -1;
		}
		cout << "init socket version sucess" << endl;
	}
	SOCKET fd;
	{
		// setp2:创建套接字 ,我们使用的是ipv4,流式、TCP
		//parm1: af 地址协议族 ipv4 ipv6
		//parm2:type 传输协议类型 流式套接字(SOCK_STREAM),数据包套接字(SOCK_DGRAM)
		//parm3:ptotoc1 使用具体的某个传输协议
		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;

		//setp2.1需要绑定的参数,主要是本地的socket的一些信息。
		SOCKADDR_IN addr;
		addr.sin_family = AF_INET;                           //地址协议族,和创建codket时必须一样。
		addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");  //127.0.0.1
		//addr.sin_addr.S_un.S_addr = INADDR_ANY;            //INADDR_ANY:绑定到本地网卡的任意地址
		addr.sin_port = htons(8888);                         //端口 htons将无符号短整型转化为网络字节序

		//绑定ip端口号
		errCode = bind(fd, (SOCKADDR*)&addr, sizeof(SOCKADDR));
		if (SOCKET_ERROR == errCode)
		{
			cout << "bind ip port faile" << endl;
		}
		cout << "bind ip port sucess" << endl;

		//step2.3监听
		listen(fd, 5);
		cout << "create socket sucess!" << endl << "begin listen..." << endl << endl;
	}
	SOCKET fd_server;
	{
		//setp3,链接服务端
		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)
	{
		//step3.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);
	}
	//关闭服务端的socket
	closesocket(fd_server);//关闭
	closesocket(fd);//关闭
	WSACleanup();//释放资源
	return 0;
}
  • cmakelist
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、客户端

  • 过程
    • 初始化
    • 创建socket
    • 链接服务端
    • 发送数据
  • main.cpp
#include <winsock2.h>
#include<windows.h>
#include <iostream>
//#pragma comment (lib,"ws2_32.lib") 因为cmake里面我们使用了target_link_libraries(Server "Ws2_32"),所以,在次我们不需要使用静态链接了。否则你需要把这行放开
using namespace std;
int main()
{
	int errCode = 0;
	cout << "==============socket client begin start.==============" << endl;
	
	{
		// step1:初始化套接字版本
		cout << "begin init socket." << endl;
		WSADATA wsadata;//wsa 即windows socket async 异步套接字
		errCode = WSAStartup(MAKEWORD(2, 2), &wsadata);   // parm1:请求的socket版本 2.2、2.1、2.0 ;parm2:传出的参数
		if (0 != errCode)
		{
			cout << "init socket version faile" << endl;
			return -1;
		}
		cout << "init socket version sucess" << endl;
	}
	SOCKET fd;
	{
		// setp2:创建套接字 ,我们使用的是ipv4,流式、TCP
		//parm1: af 地址协议族 ipv4 ipv6
		//parm2:type 传输协议类型 流式套接字(SOCK_STREAM),数据包套接字(SOCK_DGRAM)
		//parm3:ptotoc1 使用具体的某个传输协议
		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;

		//setp2.1需要绑定的参数,主要是本地的socket的一些信息。
		SOCKADDR_IN addr;
		addr.sin_family = AF_INET;                           //地址协议族,和创建codket时必须一样。
		addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");  //127.0.0.1
		//addr.sin_addr.S_un.S_addr = INADDR_ANY;            //INADDR_ANY:绑定到本地网卡的任意地址
		addr.sin_port = htons(8888);                         //端口 htons将无符号短整型转化为网络字节序

		//setp3,接收客户端的链接
		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)
	{
		//step3.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;
		}

		// step3.2,接收数据
		char receiveBuf[1024];
		errCode = recv(fd, receiveBuf, 1024, 0);
		if (errCode <= 0)
		{
			cout << "receive data faile" << endl;
		}
		cout << "receive>" << receiveBuf << endl;
	}

	//关闭客户端的socket
	closesocket(fd);//关闭
	WSACleanup();//释放资源
	return 0;
}
  • cmakelist
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、第三方网络库对各个系统的兼容性、和性能未知,所以如果要做跨平台开发的化,可以再多做些调研。
 

标签:socket,int,32,C++,接字,链接,服务端
From: https://www.cnblogs.com/lidabo/p/17223063.html

相关文章

  • 【并发编程三】C++进程通信——管道(pipe)
     【并发编程三】C++实现通信——管道(pipe)一、管道(pipe)二、匿名管道1、简介2、父子进程:匿名管道的通信过程?3、相关函数3.1、创建管道CreatePipe3.2、写入管......
  • 【并发编程六】c++进程通信——信号量(semaphore)
     【并发编程六】c++进程通信——信号量(semaphore)一、概述二、信号量三、原理四、过程1、进程A过程2、进程B过程五、demo1、进程A2、进程B六、输出......
  • C++11中的:移动语义(std::move)、完美转发(std::forward)、std::ref和std::cref
    左值(lvalue)与右值(rvalue)左值与右值的概念其实在C++0x中就有了。概括的讲,凡是能够取地址的可以称之为左值,反之称之为右值,C++中并没有对左值和右值给出明确的定义,从其......
  • C/C++的const修饰符用法总结
    已经玩了C++三年的菜鸟一枚,因此本文部分内容可能有误,请见谅。作用:只读,不能修改。规则:const默认作用于其左边的东西,否则作用于其右边的东西。从右往左即可读懂。const......
  • C++ delete语句
    C++中的五种内存在C++中内存分为五个区:堆、栈、自由存储区、全局/静态存储区和常量存储区。堆区:用户使用new获得的内存在这里。用户需要自行管理其声明周期,也就是说一个......
  • C++ 异常
    C++异常exception《C++Primer中文版第五版》Ch5.61.try语句块和异常处理典型的异常失去数据库连接遇到意外输入如果程序的问题是输入无效,则异常处理部......
  • C++信号量实现线程间同步,windows使用SetEvent,linux使用sem_t,QT测试
     目录windows使用CreateEvent、SetEvent、ResetEvent、WaitForSingleObjectlinux使用sem_init、sem_wait、sem_trywait、sem_post、sem_destroy windows使用C......
  • socket 通讯练习 本机已通过
    usingSystem;usingSystem.Collections.Generic;usingSystem.ComponentModel;usingSystem.Data;usingSystem.Drawing;usingSystem.Linq;usingSystem.Net;usi......
  • c++ 创建对象
    1.定义类一般写在.h文件中#include<string>#include<iostream>usingnamespacestd;voidprint(charcontent[]);voidTestString1();voidTestString2();voidTe......
  • C++ string类
    小引C语言中,字符串是以\0结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,不太符合OOP(面向对象)的思想,而且底层空间......