了解了网络基础之后,今天就来学习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直接访问,主要用于新的网络协议实现的测试等。
struct sockaddr
{
unsigned short sa_family;
char sa_data[14];
};
struct sockaddr_in {
short int sin_family;
unsigned short int sin_port;
struct in_addr sin_addr;
unsigned char sin_zero[8];
};
这两个数据类型是等效的,可以相互转换,通常使用sockaddr_in更为方便
四、基于流套接字的编程流程 五、详细步骤 服务器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、源码#include2、代码说明及注意事项#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; }
3、运行结果1、 服务器是打通自己的ip地址创建网络通道,然后被动的等待客户端连接,如下图
举例:因为疫情影响,本来想去海底捞吃火锅,因为封校了出不去,但是火锅店照样开门营业,所以火锅店就像是服务器被动的等待客户主动上门吃火锅,而不会主动去学校请你去。然后店外可能有迎宾小姐,就像是accept()函数,你上门了就把你引到店里,没人上门就一直在门外被动等着。所以服务器是给别人服务的,是被动的;客户端是主动的,客户都没来服务器就只能等。
2、端口号需要使用的字节转换函数——htons()——“Host to Network Short”
把主机字节顺序转换为网络字节顺序(对无符号短型进行操作2bytes)——端口65535,10000以下是操作系统使用,自己定义需要10000以后;因为我们操作系统分为大小端,大小端顺序颠倒,没有转换顺序会出错
3、ip地址格式转换函数——inet_addr()
linux提供将点分格式的地址转于长整型数之间的转换函数。 inet_addr()能够把一个用数字和点表示IP 地址的字符串转换成一个无符号长整型。
4、linux中先运行服务器



