了解了网络基础之后,今天就来学习SOCKET网络编程
基于VS2019 C++的跨平台(Linux)开发(1.6)——网络基础
1、流式套接字(SOCK_STREAM)——后面使用
流式的套接字可以提供可靠的、面向连接的通讯流。它使用了TCP协议。TCP 保证了数据传输的正确性和顺序性。
2、数据报套接字(SOCK_DGRAM)
数据报套接字定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且不保证可靠,无差错。使用数据报协议UDP协议。
3、原始套接字。
原始套接字允许对低层协议如IP或ICMP直接访问,主要用于新的网络协议实现的测试等。
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更为方便
1、socket()初始化网络
判断是否初始化成功
创建结构体如下
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()准备网络通道
2、connect()连接
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)
返回值是表示已连接的套接字描述符
开三个客户端,陆续给服务器发送消息
#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;
}
1、 服务器是打通自己的ip地址创建网络通道,然后被动的等待客户端连接,如下图
举例:因为疫情影响,本来想去海底捞吃火锅,因为封校了出不去,但是火锅店照样开门营业,所以火锅店就像是服务器被动的等待客户主动上门吃火锅,而不会主动去学校请你去。然后店外可能有迎宾小姐,就像是accept()函数,你上门了就把你引到店里,没人上门就一直在门外被动等着。所以服务器是给别人服务的,是被动的;客户端是主动的,客户都没来服务器就只能等。
2、端口号需要使用的字节转换函数——htons()——“Host to Network Short”
把主机字节顺序转换为网络字节顺序(对无符号短型进行操作2bytes)——端口65535,10000以下是操作系统使用,自己定义需要10000以后;因为我们操作系统分为大小端,大小端顺序颠倒,没有转换顺序会出错
3、ip地址格式转换函数——inet_addr()
linux提供将点分格式的地址转于长整型数之间的转换函数。 inet_addr()能够把一个用数字和点表示IP 地址的字符串转换成一个无符号长整型。
4、linux中先运行服务器