【Linux系统编程】网络编程

目录

一、概述

1、TCP/UDP对比

2、端口号作用

二、字节序 

1、字节序是什么

2、如何理解字节序

3、字节序转换api

三、socket编程

1、流程

2、socket函数

3、socket服务端代码实现(无连接客户端)

4、socket服务端/客户端

四、实现双方聊天

五、服务端自动回复


一、概述

1、TCP/UDP对比

1.TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接;

2.TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付;

3.  TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等);

4.  每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信;

5.  TCP首部开销20字节;UDP的首部开销小,只有8个字节;

6.  TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道。

2、端口号作用

        一台拥有IP地址的主机可以提供许多服务,比如Web服务、FTP服务、SMTP服务等,这些服务完全可以通过1个IP地址来实现。

        因为IP 地址与网络服务的关系是一对多的关系。 实际上是通过“IP地址+端口号”来区 分不同的服务的。 端口提供了一种访问通道, 服务器一般都是通过知名端口号来识别的。

        例如,对于每个TCP/IP实现来说,FTP服务器的TCP端口号都是21,每个Telnet服务器的TCP端口号都是23,每个TFTP(简单文件传送协议)服务器的UDP端口号都是69。 

二、字节序 

如何理解字节序 - 知乎 (zhihu.com)

1、字节序是什么

        字节序是计算机存储多字节数据的方式,目前主流的方式有:大端字节序和小端字节序,字节序主要是针对多字节的数据类型,比如 short、int 等类型

  • 大端字节序(Big endian)

高位字节存储在内存的低地址上,低位字节存储在内存的高位地址上

  • 小端字节序(Little endian)

高位字节存储在内存的高地址上,低位字节存储在内存的低地址上

        网络字节序 = 大端字节序

2、如何理解字节序

比如:对于 0x01020304,它的大端和小端字节序在内存中的布局如下图所示

        0x 01 02 03 04 总共四个字节大小,以人们习惯的阅读顺序,0x01 处于左边,属于高位字节,0x04 处于右边,属于低位字节;内存地址从 0x 00 00 00 07 到 0x 00 00 00 0A 4个字节的空间,刚好能存储得下。

        根据大端字节序的的规则:高位字节存储在内存低地址,所以处于高位字节的 0x01 存储在 0x 00 00 00 07 地址处,紧接着 次高位字节 0x02 存储在次低地址 0x 00 00 00 08 处,剩下的两个字节 0x03 和 0x04 分别存储于 0x 00 00 00 09 和 0x 00 00 00 0A 地址处,最终以 0x 01 02 03 04 存储。

        小端字节序和大端刚好相反,它指的是 高位字节存储在内存高地址处,所以处于高位字节的 0x01 存储在 0x 00 00 00 0A 地址处,次高位字节 0x02 存储在次高地址 0x 00 00 00 09 处,余下的 0x03 和 0x04 分别存储于 0x 00 00 00 08 和 0x 00 00 00 07 地址处,最终以 0x 04 03 02 01 存储。

3、字节序转换api

#include <netinet/in.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个字节),通过上面的4个函数可以实现主机字节序和网络字节序之间的转换。有时可以用INADDR_ANY,INADDR_ANY指定地址让操作系统自己获取

三、socket编程

1、流程


服务器实现步骤:

  1. 使用socket()函数创建套接字
  2. 为套接字添加信息(IP地址和端口号)
  3. listen()设置套接字为监听模式,使服务器进入被动打开状态
  4. 接收客户端的连接请求
  5. 接收、应答客户端的数据请求
  6. 关闭套接字,终止连接

客户端实现步骤

  1. 使用socket()函数创建套接字
  2. 调用connect()函数建立一个与TCP服务器的连接
  3. 发送数据请求,接收服务器的数据应答
  4. 终止连接

2、socket函数

  • socket()

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

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

  • bind() 

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
//功能:用于绑定IP地址和端口号到socketfd
//参数:
//sockfd:是一个socket描述符
//addr:是一个指向包含有本机IP地址及端口号等信息的sockaddr类型的指针,指向要绑定给sockfd的协议地址结构这个地址结构根据地址创建socket 时的地址协议族的不同而不同

  • listen()

int listen(int sockfd, int backlog);
//参数:
//sockfd:sockfd是socket系统调用返回的服务器端socket描述符
//backlog:backlog指定在请求队列中允许的最大请求数

  • accept()

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

  • connect()

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

  •  数据收发

  •  地址转换API

int inet_aton(const char* straddr,struct in_addr *addrp);
//把字符串形式的“192.168.1.123”转为网络能识别的格式

char* inet_ntoa(struct in_addr inaddr); 
//把网络格式的ip地址转为字符串形式

3、socket服务端代码实现(无连接客户端)

server1.c

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
//#include <linux/in.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
 
int main()
{
        int s_fd;
 
        //1.socket
        s_fd = socket(AF_INET,SOCK_STREAM,0);
        if(s_fd == -1){
                perror("socked");
                exit(-1);
        }
 
        struct sockaddr_in s_addr;
        s_addr.sin_family = AF_INET;    //TPv4因特网域
        s_addr.sin_port = htons(8989);  
        inet_aton("192.168.47.128",&s_addr.sin_addr);        //虚拟机linux的ip地址
 
        //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("connect\n");
        while(1);
 
        return 0;
}

 

 4、socket服务端/客户端

server2.c

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
//#include <linux/in.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
 
 
int main()
{
        int s_fd;
        int n_read;
        char readBuf[128];//定义一个数组
 
        char *msg = "I got your message";
        struct sockaddr_in s_addr;
        struct sockaddr_in c_addr;
        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);
        if(s_fd == -1){
                perror("socked");
                exit(-1);
        }
 
        s_addr.sin_family = AF_INET;
        s_addr.sin_port = htons(8989);
        inet_aton("192.168.47.128",&s_addr.sin_addr);
 
        //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);//长度
        int c_fd = accept(s_fd,(struct sockaddr *)&c_addr, &clen);
        
        if(c_fd == -1){
                perror("accept");
        }
        //成功则打印客户端ip地址
        printf("get connect:%s\n",inet_ntoa(c_addr.sin_addr));    //inet_ntoa()函数,把网络格式的 IP 地址转为字符串形式
 
//==================服务端与客户端之间数据交互=============================

        //5.read
        n_read = read(c_fd,readBuf,128);//把客户端的内容读到readBuf
        
        if(n_read == -1){
                perror("read");
        }else{
                printf("get message: %d,%s\n",n_read,readBuf);//打印readBuf中内容
        }
 
        //6.write
        write(c_fd,msg,strlen(msg));//写入操作
 //======================================================================
        return 0;
}

client.c 

#include <stdio.h>
#include <sys/types.h>      
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>


int main()
{
        int c_fd;
        int n_read;
        char readBuf[128];
   	
   	char *msg = "msg from client";
        struct sockaddr_in c_addr;
        memset(&c_addr,0,sizeof(struct sockaddr_in));
 
        //1.socket
        c_fd = socket(AF_INET,SOCK_STREAM,0);
        if(c_fd == -1){
                perror("socked");
                exit(-1);
        }
 
        c_addr.sin_family = AF_INET;
        c_addr.sin_port = htons(8989);
        inet_aton("192.168.47.128",&c_addr.sin_addr);
 
        //2.connect
        if(connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr_in)) == -1){
                perror("connect");
                exit(-1);            //连接不到服务端会阻塞,出错直接跳出程序,以免搞崩代码
        }
        //3.send
        write(c_fd,msg,strlen(msg));
 
//==================读取服务端的信息==================
        //4.read
        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;
}

四、实现双方聊天

BUG:多个客户端接入时,服务端发送的数据,不知道哪个客户端会收到。

原因:在光标输入时,不知道是哪个子进程在运行gets()。可以设置服务端自动回复。

服务端:

#include <stdio.h>
#include <sys/types.h>      
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>


int main(int argc, char **argv)
{
	int s_fd;
	int c_fd;
	int n_read; 
	int n_write;
	int mark=0;
	char readBuf[128];
	char returnMsg[128]={0};
	
	struct sockaddr_in c_addr;
	struct sockaddr_in s_addr;
	memset(&c_addr,0,sizeof(struct sockaddr_in));
	memset(&s_addr,0,sizeof(struct sockaddr_in));//数据清空
	
	if(argc != 3){
		printf("参数出错\n");
		exit(-1);
	}
	
	//1.socket
	s_fd=socket(AF_INET,SOCK_STREAM,0);        //ipv4   tcp协议
	if(s_fd == -1){
		printf("创建socket失败");
		perror("socket:");
		exit(-1);
	}
	
	s_addr.sin_family = AF_INET;//ipv4
	s_addr.sin_port = htons(atoi(argv[2]));//端口号,选择5000以上。honts返回网络字节序,atoi(argv[2])防止端口被占用
	inet_aton(argv[1],&s_addr.sin_addr);//转换为网络能识别的格式
	
	//2.bind
	bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));

	//3.listen
	listen(s_fd,10);//监听10个连接

	//4.accept
	int clen = sizeof(struct sockaddr_in);
	while(1){//不断接收客户端
		c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&clen);
		if(c_fd == -1){
			printf("连接失败\n");
			perror("accept:");
			exit(-1);
		}
		printf("客户端的ip:%s\n",inet_ntoa(c_addr.sin_addr)); //把网络格式的ip地址打印成字符串格式
		mark++;
		if(fork() == 0){
			if(fork() == 0){
				while(1){
					//sprintf(returnMsg,"欢迎第%d号客户端",mark);
					memset(returnMsg,0,sizeof(returnMsg));
					printf("请输入:\n");
					gets(returnMsg);
					n_write=write(c_fd,returnMsg,strlen(returnMsg));
				}
			}
			while(1){
				memset(readBuf,0,sizeof(readBuf));
				n_read=read(c_fd,readBuf,128);
				if(n_read == -1){
					perror("read:");
				}else{
					printf("客户端(%d)-->:%s\n",mark,readBuf);
				}
			}
		}
	}
	return 0;
}


客户端:

#include <stdio.h>
#include <sys/types.h>      
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>


int main(int argc, char **argv)
{

	int c_fd;
	int n_read;
	int n_write;
	int c_connect;
	char readBuf[128];
	char returnMsg[128]={0};
	char *quit="quit";
	struct sockaddr_in c_addr;
	memset(&c_addr,0,sizeof(struct sockaddr_in));//数据清空
	if(argc != 3){
		printf("参数出错\n");
		exit(-1);
	}
	//1.socket  int socket(int domain, int type, int protocol);
	c_fd=socket(AF_INET,SOCK_STREAM,0);
				//ipv4   tcp协议
	if(c_fd == -1){
		printf("创建socket失败");
		perror("socket:");
		exit(-1);

	}
	c_addr.sin_family=AF_INET;//ipv4
	c_addr.sin_port=htons(atoi(argv[2]));//端口号,选择5000以上。honts返回网络字节序
	inet_aton(argv[1],&c_addr.sin_addr);//转换为网络能识别的格式
	
	//2.connect  int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
    c_connect=connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr));
    if(c_connect == -1){
    	printf("连接失败\n");
    	perror("connect:");
    }

	while(1){
		if(fork() == 0){
			while(1){//不断写入
				memset(returnMsg,0,sizeof(returnMsg));
				printf("请输入:\n");
				gets(returnMsg);
				//3.write/send ssize_t write(int fd, const void *buf, size_t count);
				n_write=write(c_fd,returnMsg,strlen(returnMsg));
				if(strcmp(quit,returnMsg) == 0){//如果输入quit则客户端就退出
					exit(0);
				}
			}
		}
		while(1){//不断读取
			memset(readBuf,0,sizeof(readBuf));//不断清空数据防止数据重复出现
			//4.read  ssize_t read(int fd, void *buf, size_t count);
			n_read=read(c_fd,readBuf,128);
			if(n_read == -1){
				perror("read:");
			}else{
				printf("服务端-->:%s\n",readBuf);
			}
		}
		//5.close     int close(int fd);
		close(c_fd);
	}
	return 0;
}


 五、服务端自动回复

修改服务端部分:

while(1){//不断接收客户端
		c_fd = accept(s_fd,(struct sockaddr *)&c_addr,&clen);
		if(c_fd == -1){
			printf("连接失败\n");
			perror("accept:");
			exit(-1);

		}
		printf("客户端的ip:%s\n",inet_ntoa(c_addr.sin_addr)); //把网络格式的ip地址打印成字符串格式
		mark++;
		if(fork() == 0){
			if(fork() == 0){
				while(1){
					sprintf(returnMsg,"客户端%d连接正常",mark);
					n_write=write(c_fd,returnMsg,strlen(returnMsg));
					sleep(5);
				}
			}
			while(1){
				memset(readBuf,0,sizeof(readBuf));
				n_read=read(c_fd,readBuf,128);
				if(n_read == -1){
					perror("read:");
				}else{
					printf("客户端(%d)-->:%s\n",mark,readBuf);
				}
			}
		}
	}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值