基于VS2019 C++的跨平台(Linux)开发(2.2)——SOCKET网络编程

了解了网络基础之后,今天就来学习SOCKET网络编程

基于VS2019 C++的跨平台(Linux)开发(1.6)——网络基础

一、socket概述

  • 是为了简化开发通信程序的工作,由Berkely学校开发了一套网络通信程序的API函数标准Socket。
  • linux中的网络编程通过socket接口实现。Socket既是一种特殊的IO,它也是一种文件描述符。一个完整的Socket 都有一个相关描述{协议,本地地址,本地端口,远程地址,远程端口};每一个Socket 有一个本地的唯一Socket 号,由操作系统分配。

二、socket分类

1、流式套接字(SOCK_STREAM)——后面使用
流式的套接字可以提供可靠的、面向连接的通讯流。它使用了TCP协议。TCP 保证了数据传输的正确性和顺序性。
2、数据报套接字(SOCK_DGRAM)
数据报套接字定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且不保证可靠,无差错。使用数据报协议UDP协议。
3、原始套接字。
原始套接字允许对低层协议如IP或ICMP直接访问,主要用于新的网络协议实现的测试等。

三、sockaddr、sockaddr_in结构体

struct sockaddr
 {
	unsigned short sa_family; /* address族, AF_xxx IPV4使用*/
	char sa_data[14]; 	  /* 14 bytes的协议地址 包含了一些远程电脑的地址、端口和套接字的数目,它里面的数据是杂溶在一起的。*/
};


struct sockaddr_in {
    short int sin_family; /* Internet地址族 */
    unsigned short int sin_port; /* 端口号 */
    struct in_addr sin_addr; /* Internet地址 */
    unsigned char sin_zero[8]; /* 添0(和struct sockaddr一样大小)*/
};

这两个数据类型是等效的,可以相互转换,通常使用sockaddr_in更为方便

四、基于流套接字的编程流程

基于VS2019 C++的跨平台(Linux)开发(2.2)——SOCKET网络编程_第1张图片

 五、详细步骤

服务器

1、socket()初始化网络
        判断是否初始化成功
        创建结构体如下

  •         确定使用哪个协议簇
  •         系统获取本机ip地址:搭建网络的时候用自己的ip地址,是服务器等其他客户端来连接
  •         端口号(用inet_addr()转换)

       sizeof()求结构体长度
2、bind()绑定
        判断是否绑定成功(失败原因:ip地址出错或端口号被占用)

3、listen()监听
        监听这个地址和端口有没有客户端来连接
        判断是否监听成功

到目前为止网络还没有被打通,只是网络通道准备好了,这时要开个死循环保证服务器长时间在线(N×24h工作---等待客户端上线)

4、accept()等待客户端上线
        其中返回的acceptfd描述符表示已经连接上来的客户端。如果客户端没连接上来(没有调用connect()),accept就一直阻塞,就无法返回acceptfd,就导致没法执行后续代码,直到客户端上线才触发后续操作。( 其中acceptfd的值如果为0、1、2分别表示标准输入、输出、报错;3表示IO文件描述符;4表示网络通道)
        返回acceptfd后,继续循环,等下个客户端。

5、fork()子进程读取信息
        每来一个客户端,就fork一个子进程读取信息
        子进程循环read()读客户端发送的信息读到buf里,读一次清空一次

客户端

1、socket()准备网络通道

  • 参数中没有ip和协议端口号——不做绑定操作——而是由connect绑定 
  • 定义结构体
  • 得到协议簇、ip地址(服务器的ip地址,用inet_addr()转换)、端口号
  • sizeof()求结构体长度

2、connect()连接

  • 判断是否连接成功
  • while死循环——控制台输入

3、write ()对应服务器read
        每个客户端相互不影响,客户端间是否可以相互通信——可以,要等服务器发回来

 五、函数原型

accept——阻塞式函数

功能
accept 函数由 TCP 服务器调用,用于从已完成连接队列队头返回下一个已完成连接。如果已完成连接队列为空,那么进程被投入睡眠。

#include
#include  
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

参数说明

  • sockfd是socket系统调用返回的服务器端socket描述符
  • addr用来返回已连接的对端(客户端)的协议地址
  • addrlen表示结构体长度(使用sockaddr_in要强转成sockaddr)

返回值是表示已连接的套接字描述符

六、示例

开三个客户端,陆续给服务器发送消息

1、源码

#include 
#include 
#include    
#include 
#include 
#include 
#include //cout
using namespace std;

int main() {
	
	struct sockaddr_in s_addr;

	int socketfd = 0;
	int length = 0;
	int acceptfd = 0;//客户端的文件描述符
	char ser_buf[66] = { 0 };
	int pid = 0;
	//初始化网络
	socketfd = socket(AF_INET,SOCK_STREAM,0);
	if (socketfd == -1)
	{
		perror(" socket error");
	}
	else
	{
		//确定使用那个协议族 ipv4
		s_addr.sin_family = AF_INET;
		//系统自动获取本机ip地址
		s_addr.sin_addr.s_addr = INADDR_ANY;
		//端口65535,10000以下是操作系统使用,自己定义需要10000以后
		s_addr.sin_port = htons(10086);

		length = sizeof(s_addr);
		//绑定ip地址和端口号
		if (bind(socketfd,(struct sockaddr*)&s_addr,length) == -1)
		{
			perror(" bind error");
		}
		//监听这个地址和端口有没有客户端连接

		if (listen(socketfd,10) == -1)
		{
			perror(" listen error");
		}
		cout << "服务器网络通道准备好了" << endl;

		//死循环保证服务器长时间在线
		while (true)
		{
			cout << "等待客户端上线" << endl;
			//等待客户端上线,地址和端口号已经设置过了,所以为null,如果没有客户端访问则一直被动等待
			//返回值就表示那个客户端(给客户端发消息不需要知道客户端的ip地址)
			acceptfd = accept(socketfd, NULL, NULL);//阻塞函数

			cout << "客户端连接成功  acceptfd = " << acceptfd << endl;

			pid = fork();
			if (pid == 0)
			{
				read(acceptfd, ser_buf, sizeof(ser_buf));//注意这里是acceptfd不是socketfd
			//cout << "客户端  ser_buf = " << ser_buf << endl;
				cout << "客户端  acceptfd = " << acceptfd << "说: " << ser_buf << endl;
				bzero(ser_buf, sizeof(ser_buf));
			}
			
		}
	}
	


	return 0;
}



int main() {

	struct sockaddr_in s_addr;

	int socketfd = 0;
	int length = 0;
	int acceptfd = 0;//客户端的文件描述符
	char cli_buf[66] = { 0 };
	//初始化网络
	socketfd = socket(AF_INET, SOCK_STREAM, 0);//AF_INET表示使用ipv4,SOCK_STREAM表示流式套接字
	if (socketfd == -1)
	{
		perror(" socket error");
	}
	else
	{
		
		//确定使用那个协议族 ipv4
		s_addr.sin_family = AF_INET;
		//连接服务器的地址
		s_addr.sin_addr.s_addr = inet_addr("127.0.0.1");//ip地址的转换
		s_addr.sin_port = htons(10086);
		
		length = sizeof(s_addr);
		//准备通道不做绑定操作而是直接连接
		if (connect(socketfd, (struct sockaddr*)&s_addr, length) == -1)
		{
			perror(" connect error");
		}

		else
		{
			cout << "客户端连接成功  acceptfd = "  << endl;
			while (true)
			{
				//控制台输入
				cin >> cli_buf;
				write(socketfd, cli_buf,sizeof(cli_buf));
				bzero(cli_buf, sizeof(cli_buf));
			}
		}

		
	}



	return 0;
}


2、代码说明及注意事项

1、 服务器是打通自己的ip地址创建网络通道,然后被动的等待客户端连接,如下图

基于VS2019 C++的跨平台(Linux)开发(2.2)——SOCKET网络编程_第2张图片

举例:因为疫情影响,本来想去海底捞吃火锅,因为封校了出不去,但是火锅店照样开门营业,所以火锅店就像是服务器被动的等待客户主动上门吃火锅,而不会主动去学校请你去。然后店外可能有迎宾小姐,就像是accept()函数,你上门了就把你引到店里,没人上门就一直在门外被动等着。所以服务器是给别人服务的,是被动的;客户端是主动的,客户都没来服务器就只能等。        

2、端口号需要使用的字节转换函数——htons()——“Host to Network Short”

把主机字节顺序转换为网络字节顺序(对无符号短型进行操作2bytes)——端口65535,10000以下是操作系统使用,自己定义需要10000以后;因为我们操作系统分为大小端,大小端顺序颠倒,没有转换顺序会出错

3、ip地址格式转换函数——inet_addr()

linux提供将点分格式的地址转于长整型数之间的转换函数。 inet_addr()能够把一个用数字和点表示IP 地址的字符串转换成一个无符号长整型。

4、linux中先运行服务器

3、运行结果 

基于VS2019 C++的跨平台(Linux)开发(2.2)——SOCKET网络编程_第3张图片

你可能感兴趣的:(服务器,Linux,c++,linux,vs,socket)