Linux网络编程:Socket套接字编程(Server服务器 Client客户端)

文章目录:

一:定义和流程分析

1.定义

2.流程分析

3.网络字节序

二:相关函数

[IP地址转换函数inet_pton inet_ntop(本地字节序 网络字节序)](#IP地址转换函数inet_pton inet_ntop(本地字节序 网络字节序))

socket函数(创建一个套接字)

bind函数(给socket绑定一个服务器地址结构(IP+port))

listen函数(设置最大连接数或者说能同时进行三次握手的最大连接数监听上限)

[accept函数(阻塞监听等待客户端建立连接, 成功的话返回一个与客户端成功连接的socket文件描述符)](#accept函数(阻塞监听等待客户端建立连接, 成功的话返回一个与客户端成功连接的socket文件描述符))

connect函数(使用现有的socket与服务器建立连接)

三:服务器模型和客户端模型的实现

Server服务器的实现

Client客户端的实现


一:定义和流程分析

1.定义

复制代码
定义:一个文件描述符指向一个套接字(该套接字内部由内核借助两个缓冲区实现)
         在通信过程中, 套接字一定是成对出现的
        一种文件类型,伪文件,不占用存储空间,可进行IO操作,可间接看做文件描述符使
        Socket本身有"插座"的意思
        在Linux环境下,用于表示进程间网络通信的特殊文件类型。本质为内核借助缓冲区形成的伪文件

    管道, 套接字, 块设备, 字符设备;
    套接字: 一个fd可以索引读写两个缓冲区;



套接字的作用
    套接字是网络通信中的一种端点,它提供了应用层进程利用网络协议交换数据的机制
    套接字可以用于不同主机上的应用进程之间进行双向通信,使得它们可以交换数据、同步连接状态、处理错误等

    在计算机系统中,套接字通常被实现为文件描述符,用于在网络上进行数据传输
    当应用程序打开一个套接字时,操作系统会为它分配一个唯一的文件描述符,以便于进程间通信

2.流程分析

复制代码
socket():创建一个套接字, 用fd索引

bind():绑定IP和port

listen():设置监听上限(同时与Server建立连接数)

accpet():阻塞监听客户端连接(传入一个上面创建的套接字, 传出一个连接的套接字)

在客户端中的connect()中绑定IP和port,并建立连接(阻塞)

3.网络字节序

复制代码
小端法:(pc本地存储)	高位存高地址。低位存低地址。	int a = 0x12345678
大端法:(网络存储)	高位存低地址。低位存高地址。

htonl --> 本地--》 网络 (IP)			192.168.1.11 --> string --> atoi --> int --> htonl --> 网络字节序
htons --> 本地--》 网络 (port)
ntohl --> 网络--》 本地(IP)
ntohs --> 网络--》 本地(Port)

用库函数做网络字节序和主机字节序的转换

复制代码
#include<arpa/inet.h>
uint32_t htonl(uint32_t hostlong);			//主要针对IP
uint16_t htons(uint16_t hostshort);			//主要针对port
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);

二:相关函数

IP地址转换函数inet_pton inet_ntop(本地字节序 网络字节序)

由于如192.168.45.2这种的IP地址为点分十进制 表示,需要转化为uint32_t型,有现成的函数(IPv4和IPv6都可以转换)

复制代码
//本地字节序(string IP) ---> 网络字节序
int inet_pton(int af, const char *src, void *dst);		                   
	af:AF_INET、AF_INET6
	src:传入,IP地址(点分十进制)
	dst:传出,转换后的 网络字节序的 IP地址。 
	返回值:
		成功: 1
		异常: 0, 说明src指向的不是一个有效的ip地址。
		失败:-1


//网络字节序 ---> 本地字节序(string IP)
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);	
	af:AF_INET、AF_INET6
	src: 网络字节序IP地址
	dst:本地字节序(string IP)
	size: dst 的大小。
	返回值: 成功:dst、失败:NULL

socket函数(创建一个套接字)

复制代码
#include <sys/socket.h>


int socket(int domain, int type, int protocol);		创建一个 套接字

    domain指定使用的协议(IPv4或IPv6)
	    AF_INET 这是大多数用来产生socket的协议,使用TCP或UDP来传输,用IPv4的地址
	    AF_INET6 与上面类似,不过是来用IPv6的地址
	    AF_UNIX 本地协议,使用在Unix和Linux系统上,一般都是当客户端和服务器在同一台及其上的时候使用

    type指定数据传输协议(流式或报式)
        SOCK_STREAM 这个协议是按照顺序的、可靠的、数据完整的基于字节流的连接。这是一个使用最多的socket类型,这个socket是使用TCP来进行传输。
	    SOCK_DGRAM 这个协议是无连接的、固定长度的传输调用。该协议是不可靠的,使用UDP来进行它的连接。
	    SOCK_SEQPACKET该协议是双线路的、可靠的连接,发送固定长度的数据包进行传输。必须把这个包完整的接受才能进行读取。
	    SOCK_RAW socket类型提供单一的网络访问,这个socket类型使用ICMP公共协议。(ping、traceroute使用该协议)
	    SOCK_RDM 这个类型是很少使用的,在大部分的操作系统上没有实现,它是提供给数据链路层使用,不保证数据包的顺序

    指定代表协议(一般默认传0)protocol: 0 
        流式以TCP为代表;
        报式以UDP为代表;

    返回值:返回指向新创建的socket的文件描述符
        成功:返回新套接字所对应文件描述符fd
        失败:返回-1并设置errno;

bind函数(给socket绑定一个服务器地址结构(IP+port))

复制代码
#include <sys/socket.h>


 int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);		给socket绑定一个 地址结构 (IP+port)

	sockfd: socket文件描述符
		struct sockaddr_in servaddr;
		addr.sin_family = AF_INET;
		addr.sin_port = htons(8888);
		addr.sin_addr.s_addr = htonl(INADDR_ANY);

    addr: 构造出IP地址加端口号
        传入参数(struct sockaddr *)&addr

    addrlen: sizeof(addr) 地址结构的大小


	返回值:
		成功:0
		失败:返回-1, 设置errno

listen函数(设置最大连接数或者说能同时进行三次握手的最大连接数监听上限)

复制代码
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>


int listen(int sockfd, int backlog);        //设置同时与服务器建立连接的上限数(同时进行3次握手的客户端数量)

    sockfd:
        socket文件描述符

    backlog:上限数值。最大值 128
	    排队建立3次握手队列和刚刚建立3次握手队列的链接数和

    返回值:
		成功:0
		失败:-1 errno	

accept函数(阻塞监听等待客户端建立连接, 成功的话返回一个与客户端成功连接的socket文件描述符)

复制代码
#include <sys/types.h> 		/* See NOTES */
#include <sys/socket.h>


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

    sockfd:
	    socket文件描述符

    addr:成功与Sever建立连接的那个**客户端**的地址结构;
	    传出参数,返回链接客户端地址信息(IP地址+端口号)

    addrlen:传入传出参数(值-结果),传入sizeof(addr)大小,函数返回时返回真正接收到地址结构体的大小	    
​	    socklen_t clit_addr_len=sizeof(addr);
​	    入: 传入addr的大小;
​	    出: 客户端addr的实际大小;

    返回值:    
​	    成功: 返回能与客户端进行通信的socket对应的文件描述符;
​	    失败: 返回-1并设置errno;



//我们的服务器程序结构是这样的
while (1) {
	cliaddr_len = sizeof(cliaddr);
	connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
	n = read(connfd, buf, MAXLINE);
	......
	close(connfd);
}

connect函数(使用现有的socket与服务器建立连接)

复制代码
#include <sys/types.h> 					/* See NOTES */
#include <sys/socket.h>


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

    sockdf:socket文件描述符
    	struct sockaddr_in srv_addr;		// 服务器地址结构
		srv_addr.sin_family = AF_INET;
		srv_addr.sin_port = 9527 	跟服务器bind时设定的 port 完全一致。
		inet_pton(AF_INET, "服务器的IP地址",&srv_adrr.sin_addr.s_addr);
	    

    addr:
	    传入参数,指定服务器端地址信息,含IP地址和端口号

    addrlen:
	    传入参数,服务器地址结构的长度sizeof(addr)大小

    返回值:
	   ​	成功返回0;
​	    失败返回-1并设置errno;


如果不使用`bind()`函数绑定客户端的地址结构, 会采用**"隐式绑定"**;

三:服务器模型和客户端模型的实现

Server服务器的实现

复制代码
server:
	1. socket()	创建socket

	2. bind()	绑定服务器地址结构

	3. listen()	设置监听上限

	4. accept()	阻塞监听客户端连接

	5. read(fd)	读socket获取客户端数据

	6. 小--大写	toupper()

	7. write(fd)

	8. close();

代码逻辑

复制代码
#include <stdio.h>
#include <ctype.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
 
#define SERV_PORT 9527					//端口号
 
int main(int argc, char *argv[]){
    int link_fd=0;						//建立连接的socket文件描述符
    int connect_fd=0					//用于通信的文件描述符
    int ret=0;							//用于检查是否出错
    char buf[BUFSIZ];					//缓冲区
    char client_IP[1024]				//存入客户端IP字符串
    int num=0;							//读出的字节数
    /*服务器端地址结构*/
    struct sockaddr_in serv_addr;                   	 // 定义服务器地址结构 和 客户端地址结构
		serv_addr.sin_family=AF_INET;                    // IPv4
		serv_addr.sin_port=htons(SERV_PORT);             // 转为网络字节序的 端口号
		serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);     // 获取本机任意有效IP
    
    /*成功与服务器建立连接的客户端地址结构*/
    struct sockaddr_in clint_addr;
    socklen_t clint_addr_len=sizeof(clint_addr);    	 // 获取客户端地址结构大小
        
 
 
 
    /*1.socket函数:创建用于建立连接的socket,返回的文件描述符存入link_fd*/
		//IPv4,按照顺序基于字节流的连接,指定代表协议
		link_fd=socket(AF_INET,SOCK_STREAM,0);		
		if(link_fd==-1)
			sys_err("socket error");
    
    /*2.bind函数:绑定服务器端的socket绑定地址结构(IP+port)*/
		//socket文件描述符link_fd,IP地址加端口号
		ret=bind(link_fd,(const struct sockaddr*)&serv_addr,sizeof(serv_addr));
		if(ret==-1)
			sys_err("bind error");
    
    /*3.listen函数:设定监听(连接)上线*/
    ret=listen(link_fd,128); 
    if(ret==-1)
        sys_err("listen error");
    
    /*4.accept函数:阻塞等待客户端建立连接*/
		//文件描述符,与Sever建立连接的客户端的地址结构,返回真正接收到地址结构体的大小
		connect_fd=accept(link_fd,(	struct sockaddr*)&clint_addr,&clint_addr_len);    
		if(connect_fd==-1)
			sys_err("accept error");
 
    /*建立连接后打印客户端的IP和端口号    获取客户端地址结构*/
        printf(
				"client IP:%s,client port:%d",  												//`client_IP`是前面定义的客户端IP字符串的缓冲区, 大小为1024           
				inet_ntop(AF_INET,&clint_addr.sin_addr.s_addr,client_IP,sizeof(client_IP)),		//网络字节序 ---> 本地字节序
				ntohs(clint_addr.sin_port)														//根据accept传出参数,获取客户端 ip 和 port
			  );           
    
    /*业务逻辑*/
    while(1){
        //5. read(fd)	读socket获取客户端数据
			num=read(connect_fd,buf,sizeof(buf));    // 读客户端数据
			write(STDOUT_FILENO,buf,num);            // 写到屏幕查看
 
        //6. 小--大写	toupper()
			for(i=0;i<num;i++)                       // 小写 -- 大写
				buf[i]=toupper(buf[i]);
 
        //7. write(fd)
			write(connect_fd,buf,num);               // 将大写,写回给客户端
 
        sleep(1);
    }
 
    
    //8. close()
		close(connect_fd);
		close(link_fd);
 
  
   	return 0;
}

测试命令

复制代码
`nc 127.0.0.1 9527`        //脑残命令: 向这个服务发送信息并打印回执

Client客户端的实现

复制代码
client:
	1. socket()	创建socket

	2. connect();	与服务器建立连接

	3. write()	写数据到 socket

	4. read()	读转换后的数据

	5. 显示读取结果

	6. close()

代码逻辑

复制代码
#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
 
#define SERV_PORT 9527
 
/*错误处理函数*/
void sys_err(const char* str){
	perror(str);
	exit(1);
}
 
int main(int argc, char *argv[])){
	int client_fd=0;
	int ret=0;
	int num=0;
	int cnt=10;
	char buf[BUFSIZ];
 
	//connect的参数2填入服务器的文件描述符!
	struct sockaddr_in serv_addr;
		serv_addr.sin_family=AF_INET;
		serv_addr.sin_port=htons(SERV_PORT);
    

	// 本地字节序(string IP) ---> 网络字节序
	inet_pton(AF_INET,"127.0.0.1",(void*)&serv_addr.sin_addr.s_addr);
 
 
 
    /*1. 创建socket():客户端直接创建用于连接的套接字即可*/
		client_fd=socket(AF_INET,SOCK_STREAM,0);
		if(client_fd==-1)
			sys_err("socket error");
 
    /*2. connect():将客户端套接字与服务器地址结构连接起来*/
		ret=connect(client_fd,(struct sockaddr*)&serv_addr,sizeof(serv_addr));
		if(ret!=0)
			sys_err("connect error");
    
	//业务逻辑
	while(--cnt){
		
        //3. write()	写数据到 socket
			write(client_fd,"fuckyou\n",8);
		
        //4. read()	读转换后的数据。
			num=read(client_fd,buf,sizeof(buf));
 
        //5. 显示读取结果
			write(STDOUT_FILENO,buf,num);

		sleep(1);
	}
 
    //6. close()
		close(client_fd);
 
	return 0;
}
相关推荐
A小辣椒20 小时前
TShark:Wireshark CLI 功能
linux
A小辣椒1 天前
TShark:基础知识
linux
AlfredZhao1 天前
OCI 明明分配了 200G 系统盘,为什么 df 只看到 30G?
linux·oci
AlfredZhao2 天前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户9718356334662 天前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪2 天前
linux 拷贝文件或目录到指定的位置
linux
大树883 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠3 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质3 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
bush43 天前
嵌入式linux学习记录十四、术语
linux·嵌入式