socket网络通信 03.25

在这里插入图片描述
在这里插入图片描述

整体结构类似

依然属于进程间的通信

网络三要素:

IP地址:确定网络中某一台计算机的位置

端口号:确定是哪个应用程序

通信协议:通信时,通信双方预定好的规则

应用程序是静态的,电脑运行起来就是进程

越往上,越偏向软件;越往下,越偏向硬件

数据从应用层开始,每向下一次,会打包一次,直至物理层

TCP:

1、通信时候一定要验证通信双方是否都在线(验证的是,客户端与对应的服务器是否都在线)

2、传输介质:流式IO(byte)

3、理论上,每一次的传输不会限制大小

UDP:

1、不用验证是否在线

2、传输介质:数据报文包

3、每次只能传输64KB

TCP牺牲效率,提高传输的稳定性;UDP牺牲稳定性,提升效率

在这里插入图片描述

在大部分使用情况下,TCP连接外网,UDP连接本机;UDP适合在不管对方是否在线的情况下使用

本机IP—网络回环地址:127.0.0.1

ping 127.0.0.1 ping给自己,再自己接收

IPV4:8个字节,

在这里插入图片描述

服务器:被动连接

客户端:主动发送请求

img

SOCKET分类:

•流式套接字(SOCK_STREAM):流式的套接字可以提供可靠的、面向连接的通讯流。它使用了TCP协议。TCP 保证了数据传输的正确性和顺序性。

•数据报套接字(SOCK_DGRAM):数据报套接字定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且不保证可靠,无差错。使用数据报协议UDP协议。

•原始套接字(SOCK_RAW):原始套接字允许对低层协议如IP或ICMP直接访问,主要用于新的网络协议实现的测试等。

sockaddr套接字地址结构:
struct sockaddr
 {
	unsigned short sa_family; //address族, AF_xxx 
	char sa_data[14]; 	  //14 bytes的协议地址 
};
//sa_family 一般来说, IPV4使用“AF_INET”。
//sa_data 包含了一些远程电脑的地址、端口和套接字的数目,它里面的数据是杂溶在一起的
sockaddr_in地址结构:
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一样大小)
};
struct in_addr
{
	union{
		unsigned long int s_addr;
	}
};

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

sin_port----字节顺序转换函数:

四个函数的功能:网络字节顺序与本地字节顺序之间相互转换

•htons()——“Host to Network Short” –主机字节顺序转换为网络字节顺序(对无符号短型进行操作2bytes)

• htonl()——“Host to Network Long” –主机字节顺序转换为网络字节顺序(对无符号长型进行操作4bytes)

• ntohs()——“Network to Host Short”–网络字节顺序转换为主机字节顺序(对无符号短型进行操作2bytes)

• ntohl()——“Network to Host Long ”–网络字节顺序转换为主机字节顺序(对无符号长型进行操作4bytes)

之所以需要这些函数是因为计算机数据表示存在两种字节顺序:NBO与HBO。

网络字节顺序NBO(Network Byte Order):

按从高到低的顺序存储,在网络上使用统一的网络字节顺序,可以避免兼容性问题(大端:低位存放在高字节)。

主机字节顺序(HBO,Host Byte Order):

不同的机器HBO不相同,与CPU设计有关,数据的顺序是由cpu决定的,而与操作系统无关(小端:低位存放在低字节)。

如 Intel x86结构下,short型数0x1234表示为34 12, int型数0x12345678表示为78 56 34 12 。

如IBM power PC结构下,short型数0x1234表示为12 34, int型数0x12345678表示为12 34 56 78。

由于这个原因不同体系结构的机器之间无法通信,所以要转换成一种约定的数序,也就是网络字节顺序,其实就是如同power pc那样的顺序 。在PC开发中有ntohl和htonl函数可以用来进行网络字节和主机字节的转换。

socket()函数:

功能:为了执行I/O,服务端和客户端必须先调用socket(),产生TCP套接字,作为TCP通信的传输端点;

返回:调用成功,则返回一个较小的非负整数,失败则返回-1;

int socket(int family,int type,int protocol);

//指明协议族
family=AF_INET;//IPv4协议;
family=AF_INET6;//IPv6协议;
family=AF_ROUTE;//路由套接口。

//指明产生套接字的类型
type=SOCK_STREAM;//字节流套接口,TCP常用的形式;
type=SOCK_DGRAM;//数据报套接口,UDP常用的形式 ;
type=SOCK_RAW;//原始套接口

//protocol是协议标志,一般在调用socket()时将其设置为0。(但如果是原始套接字,就需要为其指定一个常值)
bind()函数

功能:为调用socket()函数产生的套接字(socketfd)分配一个本地协议地址,建立地址与套接字(socketfd)的对于关系。

返回:该函数调用成功返回0,若出错则返回-1,并置错误号errno;

int bind(int sockfd,const struct sockaddr *server,socklen_len addrlen);
//sockfd参数是套接字函数返回的套接字描述符;
//server参数是指向特定于协议的地址结构的指针,指定用于通信的本地协议地址;
//addrlen参数是指定该套接字地址结构的长度;
listen()函数

功能:将未连接的套接字转换成被动套接字,使它处在监听模式下,指示内核应接受发向该套接字(socketfd)的连接请求。

返回:调用成功返回0,若出错则返回-1,并置errno值;

int listen(int sockfd,int backlog);
//sockfd参数是要设置的描述符;
//backlog参数规定了请求队列中的最大连接数;
accept()函数

功能:使服务器接受客户端的连接请求。函数由 TCP 服务器调用,用于从已完成连接队列队头返回下一个已完成连接。如果已完成连接队列为空,那么进程被进入睡眠。

返回:调用成功:

(1)accept()函数的返回值,已连接套接字描述符;
(2)addr参数参会客户端的协议地址,包括IP地址和端口号等;
(3)addrlen参数返回客户端地址结构的大小。

如果调用失败则返回-1,并置errno值。

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
//sockfd参数是由sockfd()函数产生的套接字描述符,在调用accept()函数前,已经调用listen()函数将此套接字变成了监听套接字;
//addr参数是用来返回连接对方(客户端)的套接字地址结构;
//addrlen参数是用来返回连接对方的套接字的结构长度
connect()函数

功能:让TCP客户端使用来配置套接字,建立一个与TCP服务器的连接。(激发TCP的三次握手过程)

返回:调用成功后返回0,若出错则返回-1;

int connect(int sockfd,const struct sockaddr *addr,socklen_t addrlen);
//sockfd参数是有sockd()函数返回的套接字描述符;
//addr参数是指向服务器的套接字地址结构的指针;
//addrlen参数是该套接字地址结构的大小
close()函数

功能:关闭套接字,并立即返回到进程。

int close(int sockfd);

特殊文件描述符号:

•标准输入STDIN_FILENO

•标准输出STDOUT_FILENO

•标准错误STDERR_FILENO

每个进程被加载后,默认打开0,1,2这三个文件描述符

htons()

头文件:#include <arpa/inet.h>

unsigned short int htons(unsigned short int hostshort);

Server

#include <sys/types.h>
#include <sys/socket.h>
#include<netinet/in.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include<iostream>
#include<stdio.h>
#include <stdlib.h>
#include <string.h>
using namespace std;

int main()
{
	struct sockaddr_in s_addr;
	int len = 0;
	char buf[50] = { 0 };
	int socketfd = 0;
	int acceptfd = 0;
	int client_num = 0;
	socketfd = socket(AF_INET, SOCK_STREAM, 0);
	if (socketfd == -1)
	{
		perror("socket error");
	}
	else
	{
		s_addr.sin_family = AF_INET;//ipv4
		s_addr.sin_addr.s_addr = INADDR_ANY;//Internet地址
		s_addr.sin_port = htons(10086);
		len = sizeof(s_addr);
		if (bind(socketfd, (struct sockaddr*)&s_addr, len) == -1)
		{
			perror("bind error");
		}

		sleep(1);
		if (listen(socketfd, 10) == -1)//监听,判断   请求队列中的最大连接数为10
		{
			perror("listen error");
		}
		cout << "ready OK" << endl;//网络通道准备好了  

		while (1)//死循环,保证服务器长时间运行
		{
			cout << "wait" << endl;
			acceptfd = accept(socketfd, NULL, NULL);//等待客户端上线,阻塞式函数,没有响应,会一直在
			//返回客户端的IP地址
			cout << "client line succuss --- acceptfd=" << acceptfd << endl;
			//网络只能从4开始,0 1 2分别表示操作系统标准输入、输出、报错;3是文件IO操作时候文件描述符号
			client_num++;
			if (!fork())//开辟子进程进行后续工作
			{
				while (1)
				{
					read(acceptfd, buf, sizeof(buf));
					printf("Get msg from client%d: %s\n", client_num, buf);
					bzero(buf, sizeof(buf));
				}
			}
		}
	}
	close(socketfd);
	return 0;
}

Client

#include <sys/types.h> 
#include <sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include<iostream>
#include<stdio.h>
#include <string.h>
using namespace std;

int main()
{
	struct sockaddr_in s_addr;
	int len = 0;
	int socketfd = 0;
	char buf[50] = { 0 };
	//
	socketfd = socket(AF_INET, SOCK_STREAM, 0);
	if (socketfd == -1)
	{
		perror("socket error");
	}
	else
	{
		s_addr.sin_family = AF_INET;
		s_addr.sin_addr.s_addr = inet_addr("127.0.0.1");//本机回环ip地址即虚拟机ip  192.168.136.0
		s_addr.sin_port = htons(10086);
		len = sizeof(s_addr);
		//客户端向服务器发送连接请求
		if (connect(socketfd, (struct sockaddr*)&s_addr, len) == -1)
		{
			perror("connect error");
		}
		else
		{
			cout << "connect success" << endl;
			while (1)
			{
				cin >> buf;
				write(socketfd, buf, sizeof(buf));
			}
		}
	}
	close(socketfd);
	return 0;
}

桥接与NAT连接:

  • 虚拟机网络在桥接连接方式下,虚拟机的ip为主机下的网段,若主机ip地址改变,则虚拟机ip也会改变

  • 虚拟机网络在NAT连接方式下,会生成始终固定的局域网段,而虚拟机的ip为网段中的一个ip地址,因此NAT连接相比桥接,有优点是虚拟机的ip不受系统ip地址变化而变化

在这里插入图片描述

在这里插入图片描述

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐