Linux系统编程--网络编程

基础知识

  • 搜索结构体

 

  • 运行结果

回顾和引入

  • 5种通信方式

    • 管道、消息队列、共享内存、信号、信号量
    • 特点:依赖于内核
    • 必然缺陷:无法多机通信 (A、B 不以一个内核来管)
  • 网络编程

    • 地址

      • IP地址 & 端口号

      • IP地址负责将数据从源设备正确地传输到目标设备。 //找到PC

        端口则确保在目标设备上,数据交付到正确的应用或服务。 //根据端口号找到应用/服务和通信协议

 

数据:协议(http、TCP/UDP)

数据格式
eg: 单片机->UART 51
Socket套接字:TCP/UDP

TCPUDP
面向连接(打电话)无连接(发短信)
面向字节流面向报文(无拥塞控制)
点对点一对一、一对多、多对一、多对多
全双工可靠信道(按序到达)不可靠信道 (尽最大努力交付)
首部开销20字节首部开销8字节

端口号的作用

1、字节序-大小端
字节序是指多字节数据在计算机内存中存储或者网络传输时各字节的存储顺序。

常见序

Little endian:小端字节序(x86): 将低序位置(低位)存储在起始位置(低地址)
Big endian: 大端字节序= 网络字节序 将高序位置(高位)存储在起始位置(低地址)

0x01020304  //已存在数据 
存储地址小端字节序大端字节序(网络)
40030000 0001(01)0000 0100(04)
40020000 0010(02)0000 0011(03)
40010000 0011(03)0000 0010(02)
40000000 0100(04)0000 0001(01)

2、socket编程介绍

  • 客户端找服务器

2.1 socket服务器与客户端的开发步骤 

2.2 Linux提供的API
2.2.1 创建套接字–socket()
  • 指定讲“汉语”(连接协议)
int socket(int domain,int type,int protocol)
//创建套接字
//返回值:错误返回 -1

int socketz(AF_INET, SOCK_STREAM, 0); //IPv4、TCP协议

domain:

指明所使用的协议族,通常为AF_INET,表示互联网协议族(TCP/IP协议族)
AF_INET IPv4因特网域
AF_INET6 IPv6因特网域
type:

指定socket的类型
SOCK_STREAM: 使用TCP协议
SOCK_DGRAM: 使用UDP协议
protocol:

通常赋值 “ 0 ”;
0 :选择type类型对应的默认协议
IPPROTO_TCP TCP传输协议
IPPROTO_UDP UDP传输协议
IPPROTO_SCTP SCTP传输协议
IPPROTO_TIPC TIPC传输协议
2.2.2 IP号、端口号与相应描述字赋值函数–bind()
地址准备好,告诉我的IP地址(楼号)& 端口号(房间号)

#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
//用于绑定IP地址和端口号到socketfd
  • sockfd

    • 是一个socket描述符
  • addr

    • 是一个指向包含有本机IP地址及端口号等信息的sockaddr类型的指针

  • struct sockaddr{
        			unisgned short as_family; //协议族
        			char  		   sa_data[14];//IP+端口
    }
    //上面这种方式用的比较少
    //同等替换
    //一般用这种
    struct sockaddr_in{
    	sa_family_t    sin_family;   /* 协议族 */
        in_port_t	   sin_port;     /* 端口号 */
        struct in_addr sin_addr;     /* IP地址结构体 */
        unsigned char  sin_zero[8];  /* 填充 没有实际意义,只是为了跟sockaddr结构在内存中对齐,以便相互转换 */
    }
    

  • addrlem

    • 参数二,结构体的大小sizeof
  • 2.2.3 地址转换API–inet_aton()&*inet_ntoa()
  • int inet_aton(const char *straddr,struct in_addr *addrp)
    //把字符串形式的 ”192.168.1.123“转为网络能识别的格式
    //参数一:字符串
    //参数二:IP
        
    char* inet_ntoa(struct in_addr inaddr);
    //把网络格式的IP地址转为字符串形式
    
  • 字节序转换api
#include <stdio.h>
uint16_t htons(uint16_t host16bitvalue);//返回网络字节序的值
uint32_t htonl(uint32_t host32bitvalue);//返回网络字节序的值
uint16_t ntohs(uint16_t net16bitvalue);//返回主机字节序的值 uint32_t
ntohl(uint32_t net32bitvalue);//返回主机字节序的值

//h代表host
//n代表net
//s代表short (两个字节)	
//l代表long(4个字节)
2.2.4 监听设置函数–listen()
#include <sys/types.h>
#include <sys/socket.h>
int listen(int sockfd, int backlog);
//sockfd:要监听的套接字
//backlog:支持的最大连接数

2.2.5 连接–accept()

//是否完成三次握手
#include <sys/types.h>
#include <sys/socket.h>
int accept(int socket, struct sockaddr *addr, socklen_t *addrlen);
//sockfd: 套接字,和文件描述符类似
//*addr:客户端地址    
//*addrlen客户端地址的长度

//返回值:已创建好连接的套接字描述符

 2.2.6 数据收发–write()&read() – send()&recv()

//第一套
ssize_t write(int fd, const void *buf, size_t nbytes);
ssize_t read(int fd,void *buf, size_t nbyte);
//函数均返回读或写的字节个数,出错则返回 -1

//write将buf中的nbytes个字节写入到文件描述符fd中; 成功时返回写的字节数;
//read为从fd中读取nbyte个字节到buf中;	成功时返回实际所读的字节数
//第二套
//在TCP套接字上发送数据函数:有连接
ssize_t send(int s, const void *msg, size_t len, int flags);
//包含3要素:套接字s、待发数据msg、数据长度len
//函数只能对处于连接状态的套接字使用,参数s为 已建立好连接的套接字描述符,即accept函数的返回值
//msg:指向存放待发送数据的缓冲区
//len:为待发送数据的长度,
//flags:为控制选项,一般设置为0

//在TCP套接字上接收数据函数:有连接
ssize_t recv(int s, void *buf, size_t len, int flags);
//包含3要素:套接字s、接收缓冲区buf、数据长度len
//函数recv从参数s所指定的套接字描述符(必须是面向连接的套接字)上接收
//接收数据并保存到参数buf所指定的缓冲区
//len:为缓存区长度,参数flags为控制选项,一般设置为0

 

  • UDP一般用recvmsg()/sendmsg()、recvfrom()/sendto();
2.2.7 客户端连接主机–connect()
  • 功能:该函数用于绑定之后的client端(客户端),与客户端建立连接
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
//sockfd:是目的服务器的socket描述符
//addr:是服务器端的IP地址和端口号的地址结构指针
//addrlen:地址长度常被设置为sizeof(struct sockaddr)
//返回值:成功返回0,遇到错误返回-1,并且errno中包含相应的错误码

3、socket服务端代码实现

3.1 服务端代码一
  • PC机连接虚拟机【实验1】
//server.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
//#include <linux/in.h>
#include <arpa/inet.h>
#include <netinet/in.h> //该头文件会与 #include <linux/in.h> 冲突
#include <arpa/inet.h>

int main()
{
	int s_fd;

	//1、socket
	s_fd = socket(AF_INET,SOCK_STREAM,0);  //ivp4,TCP
	if(s_fd == -1)
	{
		perror("socket");//将上一个函数错误的原因输出
		exit(-1);
	}

	struct sockaddr_in s_addr;
	s_addr.sin_family = AF_INET;//ivp4
	s_addr.sin_port = htons(8888);//htons() 返回网络字节的端口号
	inet_aton("192.168.1.85",&s_addr.sin_addr);//字符串类型形式的“192.0.0.1”转换为网络能识别的格式//字符串-指针

	//2、bind
	bind(s_fd, (struct sockaddr *)&s_addr, sizeof(struct sockaddr_in));
	//3、listen
	listen(s_fd,10);
	//4、accept
	int c_fd = accept(s_fd, NULL,NULL);
	//5、read

	//6、write

	printf("connet\n");
	while(1);
	return 0;
}
  • 如何验证呢?

1、先用在虚拟机里面用ifconfig命令查看ip地址

2、将**ip地址端口号**填写到server.c里面

 

3、在终端运行server.c 

程序阻塞在 accept函数

accept:是否完成三次握手

4、在window系统里面执行指令,使连接成功、

5、如果不奏效,是因为在最新的 Windows 系统版本中,默认情况下是没有安装 Telnet 客户端的。需要手动启用 Telnet 客户端功能。

Telnet 也是TCP协议,所以可以连接上。

 

6、运行成功后,结果图

 

 7、打开虚拟机,不再为阻塞状态,连接成功,输出提示符–connect

 到这里就验证了实验一

3.2 服务端_客户端 代码二-双方消息收发
  • 实验2
//server2.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h> //memset包含头文件
#include <sys/socket.h>
#include <sys/types.h>
//#include <linux/in.h>
#include <arpa/inet.h>
#include <netinet/in.h> //该头文件会与 #include <linux/in.h> 冲突
#include <arpa/inet.h>
#include <unistd.h>

int main(int argc, char **argv)
{
	int s_fd;
	int c_fd;
	char readBuf[128];

	char msg[128] ={0};
	//sockaddr_in 是一个结构体,包含本机子ip地址和端口号等信息
	struct sockaddr_in s_addr;//用于bind
	struct sockaddr_in c_addr;//用于accept

	if(argc != 3)
	{
		printf("paran is not good\n");
		exit(-1);
	}

	memset(&s_addr,0,sizeof(struct sockaddr_in));//进行清空
	memset(&c_addr,0,sizeof(struct sockaddr_in));

	//1、socket
	s_fd = socket(AF_INET,SOCK_STREAM,0);  //ivp4,TCP
	if(s_fd == -1)
	{
		perror("socket");//将上一个函数错误的原因输出
		exit(-1);
	}

	s_addr.sin_family = AF_INET;//ivp4
	s_addr.sin_port = htons(atoi(argv[2]));//htons() 返回网络字节的端口号 //argv[2]是第二个参数
	inet_aton(argv[1],&s_addr.sin_addr);//字符串类型形式的“192.0.0.1”转换为网络能识别的格式//字符串-指针

	//2、bind
	bind(s_fd, (struct sockaddr *)&s_addr, sizeof(struct sockaddr_in));
	//3、listen
	listen(s_fd,10);
	//4、accept
	int clen = sizeof(struct sockaddr_in);
	while(1)
	{
		c_fd = accept(s_fd, (struct sockaddr *)&c_addr,&clen);//accep第三个参数要求是指针//不连接,便堵塞,直到与客户端连接
		if(c_fd == -1)
		{
			perror("accept");//终端打出错误:accept:---
		}
		printf("get connect: %s\n",inet_ntoa(c_addr.sin_addr));
		if(fork() == 0)
		{
			if(fork()==0)
			{
				while(1)
				{
					memset(msg,0,sizeof(msg));
					printf("input: ");
					gets(msg);//不输出阻塞
					write(c_fd,msg,strlen(msg));
				}
			}
			//5、read
			while(1)
			{
				memset(readBuf,0,sizeof(readBuf));
				int n_read = read(c_fd,readBuf,128);
				if(n_read == -1)
				{
					perror("read");
				}
				else
				{
					printf("get message:%d,%s\n",n_read,readBuf);
				}
			}
			break;

		}
	}
	return 0;
}

//client.c
//client.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h> //memset包含头文件
#include <sys/socket.h>
#include <sys/types.h>
//#include <linux/in.h>
#include <arpa/inet.h>
#include <netinet/in.h> //该头文件会与 #include <linux/in.h> 冲突
#include <arpa/inet.h>
#include <unistd.h>

int main(int argc, char **argv)
{
	int c_fd;
	char readBuf[128];

	char msg[128] ={0};
	//sockaddr_in 是一个结构体,包含本机子ip地址和端口号等信息
	struct sockaddr_in c_addr;//用于accept

	memset(&c_addr,0,sizeof(struct sockaddr_in));

	if(argc != 3)
	{
		printf("param is not good\n");
		exit(-1);
	}

	//1、socket
	c_fd = socket(AF_INET,SOCK_STREAM,0);  //ivp4,TCP
	if(c_fd == -1)
	{
		perror("socket");//将上一个函数错误的原因输出
		exit(-1);
	}

	c_addr.sin_family = AF_INET;//ivp4
	c_addr.sin_port = htons(atoi(argv[2]));//htons() 返回网络字节的端口号
	inet_aton(argv[1],&c_addr.sin_addr);//字符串类型形式的“192.0.0.1”转换为网络能识别的格式//字符串-指针

	//2、connect
	if(connect(c_fd,(struct sockaddr*)&c_addr,sizeof(struct sockaddr)) == -1)//不连接就阻塞
	{
		perror("connect");
		exit(-1);
	}

	while(1)
	{
		if(fork()==0)
		{
			while(1)
			{
				printf("input: ");
				memset(msg,0,sizeof(msg));
				gets(msg);//不发的时候,阻塞
				write(c_fd,msg,strlen(msg));
			}
		}
		while(1)
		{
			memset(readBuf,0,sizeof(readBuf));//以免上面的数据保留
			int n_read = read(c_fd,readBuf,128);
			if(n_read == -1)
			{
				perror("read");
			}
			else
			{
				printf("get message from server:%d,%s\n",n_read,readBuf);
			}
		}
	}
	return 0;
}

实验2结果

 

  • 发送88

3.3 服务端_客户端 代码三-多方消息收发
  • 实验1

 

//server3.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h> //memset包含头文件
#include <sys/socket.h>
#include <sys/types.h>
//#include <linux/in.h>
#include <arpa/inet.h>
#include <netinet/in.h> //该头文件会与 #include <linux/in.h> 冲突
#include <arpa/inet.h>
#include <unistd.h>

int main(int argc, char **argv)
{
	int s_fd;
	int c_fd;
	char readBuf[128];
	

	int mark = 0;
	char msg[128] ={0};
	//sockaddr_in 是一个结构体,包含本机子ip地址和端口号等信息
	struct sockaddr_in s_addr;//用于bind
	struct sockaddr_in c_addr;//用于accept

	if(argc != 3)
	{
		printf("paran is not good\n");
		exit(-1);
	}

	memset(&s_addr,0,sizeof(struct sockaddr_in));//进行清空
	memset(&c_addr,0,sizeof(struct sockaddr_in));

	//1、socket
	s_fd = socket(AF_INET,SOCK_STREAM,0);  //ivp4,TCP
	if(s_fd == -1)
	{
		perror("socket");//将上一个函数错误的原因输出
		exit(-1);
	}

	s_addr.sin_family = AF_INET;//ivp4
	s_addr.sin_port = htons(atoi(argv[2]));//htons() 返回网络字节的端口号 //argv[2]是第二个参数
	inet_aton(argv[1],&s_addr.sin_addr);//字符串类型形式的“192.0.0.1”转换为网络能识别的格式//字符串-指针

	//2、bind
	bind(s_fd, (struct sockaddr *)&s_addr, sizeof(struct sockaddr_in));
	//3、listen
	listen(s_fd,10);
	//4、accept
	int clen = sizeof(struct sockaddr_in);
	while(1)
	{
		c_fd = accept(s_fd, (struct sockaddr *)&c_addr,&clen);//accep第三个参数要求是指针//不连接,便堵塞,直到与客户端连接
		if(c_fd == -1)
		{
			perror("accept");//终端打出错误:accept:---
		}
		mark++;
		printf("get connect: %s\n",inet_ntoa(c_addr.sin_addr));
		if(fork() == 0)
		{
			if(fork()==0)//再次创建线程
			{
				while(1)
				{
					sprintf(msg,"welcom No %d client",mark);
					write(c_fd,msg,strlen(msg));
					sleep(10);
				}
			}
			//5、read
			while(1)
			{
				memset(readBuf,0,sizeof(readBuf));
				int n_read = read(c_fd,readBuf,128);
				if(n_read == -1)
				{
					perror("read");
				}
				else
				{
					printf("get message:%d,%s\n",n_read,readBuf);
				}
			}
			break;

		}
	}
	return 0;
}
  • 实验结果

    • 实现了多个客户端给服务器发信息的功能

多个客户端与服务器建立连接

  • 客户端发送数据给服务器

  • 服务器只是一个中转站,负责消息的周转、不负责与客户端不断地交互发送消息,而是负责管理客户端的关系,以及客户端注册关系,可以把客户端消息记录在数据库中;

  • 客户端与客户端进行通信

 

  • 30
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值