百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

Linux网络编程——详解SOCKET

ahcoder 2025-03-03 17:17 34 浏览

一、预备知识

大端模式、小端模式

  • 大端字节序(Big Endian):最高有效位存于最低内存地址处,最低有效位存于最高内存处;
  • 小端字节序(Little Endian):最高有效位存于最高内存地址,最低有效位存于最低内存处。

网络字节序

  • 我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分,磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分。网络数据流同样有大端小端之分,也就是说,当接收端收到第一个字节的时候,它将这个字节作为高位字节还是低位字节处理
  • 网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址
  • TCP/IP协议规定:把接收到的第一个字节当作高位字节看待,因此网络数据流应采用大端字节序,即低地址高字节

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

#include 

//这些函数调用成功后返回处理后的值,调用失败则返回-1
uint32_t htonl(uint32_t hostlong);	//主机字节顺序转换为网络字节顺序 对无符号长型进行操作4bytes
uint16_t htons(uint16_t hostshort); //主机字节顺序转换为网络字节顺序 对无符号短型进行操作2bytes  

uint32_t ntohl(uint32_t netlong);   //网络字节顺序转换为主机字节顺序 对无符号长型进行操作4bytes
uint16_t ntohs(uint16_t netshort);  //网络字节顺序转换为主机字节顺序 对无符号短型进行操作2bytes

IP地址转换函数

  • Linux提供了用于将点分十进制表示的IP地址与二进制表示的IP地址相互转换的函数族

早期

#include 
#include 
#include 

//数字加点类型 转换成 将32位的IP    IP地址存放在参数straddr中,返回结果存放在addrptr中 
int inet_aton(const char *straddr, struct in_addr *addrptr);

//将32位的IP 转换成 数字加点类型
char *inet_ntoa(struct in_addr straddr);

//数字加点类型 转换成 将32位的IP
in_addr_t inet_addr(const char *cp);

/*只能处理IPv4的ip地址
不可重入函数
注意参数是struct in_addr*/

现在

#include 

int inet_pton(int af, const char *src, void *dst);
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);

/*支持IPv4和IPv6
可重入函数*/

Linuxc/c++服务器开发高阶视频,电子书学习资料后台私信【架构】获取

sockaddr数据结构

  • Linux中定义了一种通用的套接字结构类型strcut sockaddr,以供不同的协议调用
  • strcut sockaddr 很多网络编程函数诞生早于IPv4协议,那时候都使用的是sockaddr结
    构体,为了向前兼容,现在sockaddr退化成了(void *)的作用,传递一个地址给函数,至于这个地址类型是sockaddr_in还是sockaddr_in6(文章没有列举出),由地址族确定,然后函数内部再强制类型转化为所需的地址类型
#include 

struct sockaddr
 {
	unsigned short sa_family; /* address族, AF_xxx */
	char sa_data[14]; 	      /* 14 bytes的协议地址 */
};

参数sa_family可选择如下

  • AF_INET IPv4协议
  • AF_INET6 IPv6协议
  • AF_LOCAL UNIX协议
  • AF_LINK 链路地址协议
  • AF_KEY 密钥套接字

除了sockaddr以外,Linux中还定义了另外一种结构类型sockaddr_in,它和sockaddr等效且可以互相转换(需要显式转换),通常在涉及TCP/IP的编程协议中使用

#include 

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一样大小)*/
};

//其中in_addr由于历史设计原因导致结构体多余
struct in_addr
{
__be32 s_addr;//32位IPv4地址,网络字节序
};

网络设计模式

c/s 客户端/服务器

  • 需要开发客户端服务器,采用自定义协议
  • 必须先下载客户端,数据提前缓冲好
  • 需要考虑安全问题
  • 开发工作量大

b/s web/服务器

  • 不需要安装软件,点击浏览器就可以看到
  • 工作量小,客户端基本浏览器方式
  • 缺点:必须遵循http协议,动态加载数据

二、SOCKET

概述

  • linux中的网络编程通过socket接口实现。socket既是一种特殊的IO,它也是一种文件描述符

socket可以简单理解成为一个插座和插排,那么如何匹配?


就是通过IP+端口号进行匹配,匹配之后可以通过socket进行数据的发送和接收(socket本质是文件描述符fd)


具体的流程如下

socket创建

#include  
#include 

int socket(int domain, int type, int protocol);
  • domain:
    AF_INET 这是大多数用来产生socket的协议,使用TCP或UDP来传输,用IPv4的地址
    AF_INET6 与上面类似,不过是采用IPv6的地址
    AF_UNIX 本地协议,使用在Unix和Linux系统上,一般都是当客户端和服务器在同一台及其上的时候使用
  • type:
    (1)SOCK_STREAM 这个协议是按照顺序的、可靠的、数据完整的基于字节流的连接。这是一个使用最多的socket类型,这个socket是使用TCP来进行传输。
    (2)SOCK_DGRAM 这个协议是无连接的、固定长度的传输调用。该协议是不可靠的,使用UDP来进行它的连接。
    (3)SOCK_SEQPACKET 这个协议是双线路的、可靠的连接,发送固定长度的数据包进行传输。必须把这个包完整的
    接受才能进行读取。
    (4)SOCK_RAW 这个socket类型提供单一的网络访问,这个socket类型使用ICMP公共协议。(ping、traceroute使
    用该协议)
    (5)SOCK_RDM 这个类型是很少使用的,在大部分的操作系统上没有实现,它是提供给数据链路层使用,不保证数
    据包的顺序
  • protocol:
    0 默认协议
  • 返回值
    成功返回一个新的文件描述符(也叫监听套接字),失败返回-1

bind绑定

  • 在创建了套接字之后需要IP和端口号和套接字绑定在一起( IP地址:在网络环境中,唯一标识一台主机,端口号:在主机中唯一标识一个进程)
  • 前面讲过,struct sockaddr *是一个通用指针类型,addr参数实际上可以接受多种协议的sockaddr结构体,而它们的长度各不相同,所以需要第三个参数addrlen指定结构体的长度
#include 
#include 

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • sockfd
    socket文件描述符
  • addr:
    构造出IP地址加端口号
  • addrlen:
    sizeof(addr)长度
  • 返回值
    成功返回0,失败返回-1, 设置errno

例如

struct sockaddr_in servaddr;
bzero(&servaddr, sizeof(servaddr));//清0结构体
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(8000);

listen

  • 创建了套接字之后通常需要等待客户端的连接,此时可以使用listen函数将该套接字转换为倾听套接字。
  • 可以指定同时连接的最大客户端数量
  • 若达到数量上限,新客户端等待其它已链接的客户端链接结束
#include 
#include 

int listen(int sockfd, int backlog);
  • sockfd:
    socket文件描述符
  • backlog:
    排队建立3次握手队列和刚刚建立3次握手队列的连接数和
  • 返回值
    成功返回0,失败返回-1

accept

  • 当服务器倾听到一个连接之后,可以使用函数accept从倾听套接字的完成连接队列中接收一个连接,如果这个完成连接队列为空,则会使得这个进程进入睡眠状态
#include  /* See NOTES */
#include 

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
  • sockdf:
    socket文件描述符
  • addr:
    传出参数,返回链接客户端地址信息,含IP地址和端口号
  • addrlen:
    传入传出参数,传入sizeof(addr)大小,函数返回时返回真正接收到地址结构体的大小
  • 返回值
    成功返回一个新的socket文件描述符,用于和客户端通信,失败返回-1,设置errno

connect客户端连接函数

  • 客户端需要调用connect()连接服务器,connect和bind的参数形式一致,区别在于bind的参数是自己的地址,而connect的参数是对方的地址
#include  
#include 

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • sockdf:
    socket文件描述符
  • addr:
    传入参数,指定服务器端地址信息,含IP地址和端口号
  • addrlen:
    传入参数,传入sizeof(addr)大小
  • 返回值
    成功返回0,失败返回-1,设置errno

读写函数



int read(int fd, char *buf, int len);
int write(int fd, char *buf, int len);
  • fd
    套接字描述符;
  • buf
    指定数据缓冲区;
  • len
    指定接收或发送的数据量大小(以字节为单位)。
  • 返回值
    返回读/写成功的数据量大小,失败则返回-1。

关闭函数



int close(int fd);
  • fd
    套接字描述符;

写一个服务器例子和客户端例子

服务器

#include  
#include  
#include 
#include  
#include  
#include 
#include 
#include 

#define Port 6666 //端口号
#define MAXCLIENT 10 //最大客户端数量

int main(int argc, char argv[])
{
	int socket_fd, client_fd;
	int ret;
	int addr_size;
	struct sockaddr_in server_addr;   
	struct sockaddr_in client_addr; 
	
	int read_size;
	char buffer[1024]; 
	
	//创建socket
	socket_fd = socket(AF_INET, SOCK_STREAM, 0);
	if( socket_fd == -1)
	{
		printf("socket error\n");
		exit(1);
	}
	
	//绑定bind
	bzero(&server_addr, sizeof(struct sockaddr_in));//清空数据
	
	server_addr.sin_family = AF_INET;//IPv4
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);//将主机IP转换为网络IP
	server_addr.sin_port = htons(Port);//将主机端口转换为网络Port	
	
	ret = bind(socket_fd, (struct sockaddr *)(&server_addr), sizeof(struct sockaddr));
	if(ret == -1)
	{
		printf("bind error\n");
		exit(1);
	}
	
	//监听
	ret = listen(socket_fd, MAXCLIENT);
	if(ret == -1)
	{
		printf("listen error\n");
		exit(1);
	}
	
	while(1)
	{
		//accept
		addr_size = sizeof(struct sockaddr_in);
		client_fd = accept(socket_fd, (struct sockaddr *)(&client_addr), &addr_size);
		if(client_fd == -1)
		{
			printf("accept error\n");
			exit(1);
		}
		//打印客户端IP   将网络地址转换成 .字符串 
		printf("Server get connection from %s\n",inet_ntoa(client_addr.sin_addr));
			
		if((read_size = read(client_fd, buffer, 1024)) == -1)    
		{     
			printf("Read Error\n");     
			exit(1);    
		} 
  	     
		buffer[read_size]='\0';   
		printf("Server received %s\n",buffer); 
			
		close(client_fd);    /* 循环下一个 */   		
	}
	
	close(socket_fd);   	
	return 0;
}

客户端

#include  
#include  
#include 
#include  
#include  
#include 
#include 
#include 

#define Port 6666

int main(int argc, char argv[])
{
	int socket_fd;
	int ret;
	char buff[1024];
	struct sockaddr_in server_addr;
	
	char* str_IP = "172.21.252.7";
	
	//创建客户端socket
	socket_fd = socket(AF_INET, SOCK_STREAM, 0);
	if( socket_fd == -1)
	{
		printf("socket error\n");
		exit(1);
	}
	
	//连接connect
	bzero(&server_addr, sizeof(struct sockaddr_in));//清空数据
	
	server_addr.sin_family = AF_INET;//IPv4
	server_addr.sin_addr.s_addr = inet_addr(str_IP);//将主机IP转换为网络IP
	server_addr.sin_port = htons(Port);//将主机端口转换为网络Port

	ret = connect(socket_fd, (struct sockaddr*)(&server_addr), sizeof(struct sockaddr_in));
	if(ret == -1)
	{
		printf("connect error\n");
		exit(1);
	}
	
	while(1)
	{
		//连接成功了,发送数据
		printf("Please input char:\n");     
		fgets(buff, 1024, stdin);   
		write(socket_fd, buff, strlen(buff)); 
	}
	
	close(socket_fd);
	return 0;
}

运行结果如下

注意

可通过nc指令测试服务器是否有误

相关推荐

Linux ntp时钟同步问题(linux ntp同步命令)

有时候,操作系统会报linux的ntp时钟错误。从时钟源同步时间超时。我查看了一下ntp的时钟源,发现执行ntpq-p显示超时。执行nslookup时钟服务器域名,发现可以正常解析。执行了n...

linux服务器同步利器Rsync全介绍(付脚本)

1、认识Rsync(remotesynchronize)是一个远程数据同步工具,可通过LAN/WAN快速同步多台主机间的文件。Rsync使用所谓的“Rsync算法”来使本地和远程两个主机之间的文件...

如何使用Rsync同步两个网站服务器的内容

本头条号每天坚持更新原创干货技术文章,欢迎关注本头条号"Linux学习教程",公众号名称“Linux入门学习教程"。如需学习视频,请复制以下信息到手机浏览器或电脑浏览器上:zc...

Linux TB级小文件同步备份解决方案

在某些电子档案存储的业务系统下,按照业务场景要求,需要将产生的所有电子影像资料永久存储,并支持备份;因为“钞能力”等原因,一般备份存储只有一个节点(即:一个备份存储服务器);这样就要求研发工程师,不...

CentOS7下使用Lsyncd实现文件实时同步

Lsyncd简单介绍Lsyncd使用文件系统事件接口(inotify或fsevents)来监视本地文件和目录的更改。Lsyncd在几秒钟内将这些文件事件整理核对后,然后生成一个或多个进程以将更...

如何手动同步Linux系统时间并与互联网时间保持同步?

在Linux系统中,正确的时间同步对于许多任务都是至关重要的。一些关键应用程序可能需要准确的时间戳才能正常工作,而一些安全功能也需要确保所有服务器的时间都是准确的。NTP服务可以自动同步系统时间和互...

Linux服务器Rsync定时同步一键化配置指南

1、主从服务器同时安装rsyncopenssh-clientaptupdate&&aptinstall-yrsyncopenssh-client2、主服务器安装好后输入以下命令生成密钥...

Linux系统如何使用NTPDATE命令设置时间同步

简介:咱们在使用Linux系统的时候,常常能发现用了一阵子之后,Linux系统的时间就对不上了。已经知道的情况是,安装Linux的时候选的时区是Asia/Shanghai,可这系统运行了一...

开源的Linux文件同步工具有哪些?(linux 文件夹 同步)

Linux系统之间经常需要文件同步,一个好的文件同步工具省时省力,这里介绍几款开源的Linux文件同步工具1.rsync:rsync是Linux上一个功能强大的命令行工具,可以在不同的服务器之间同...

Linux 系统使用 rsync + inotify 实现文件实时同步

Linux系统使用rsync+inotify实现文件实时同步简介inotify监控文件的变化后通知rsync进行文件的同步#配置免密登录ssh-keygen-trsaca...

Linux 远程数据同步工具详解(linux远程同步命令)

一、简介1认识Rsync(remotesynchronize)是一个远程数据同步工具,可通过LAN/WAN快速同步多台主机间的文件。Rsync使用所谓的“Rsync算法”来使本地和远程两个主机之...

构建基于 Linux 的 NTP 时间同步集群与时间安全防护实战指南

一、为什么时间同步对系统如此重要?在分布式系统、数据库复制、消息队列、审计日志、安全认证(如Kerberos)、容器编排等场景中,系统时间一致性是保障正确性与安全性的基础。一旦服务器间时间漂移,可能...

每日Linux学习:同步备份工具 rsync

rsync是一个用于高效同步文件和目录的工具。它通过仅传输变化的部分(增量传输)来节省时间和带宽,支持本地同步、远程同步(通过SSH),还能保留文件原有权限、时间戳等属性。本文通过几个常用的实例来...

Chromixium OS 1.0发布 基于Ubuntu发行版

Chromixium项目宣布chromixiumOS1发布,一款基于Ubuntu的Linux分支,chromixiumOS试图重新打造一款外观和感觉与ChromeOS相似的完...

iOS8.0.X~iOS8.1完美越狱!盘古团队出品

【巴士速递·移动情报站】最新消息,iOS8.1越狱了!!此次越狱依旧是iOS7时代我国的大神盘古团队带来的,而该次越狱支持最新的iPhone6、iPhone6Plus、iPadAir2和iPad...