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

Linux网络编程——详解SOCKET linux 网络编程

liebian365 2024-10-14 22:22 25 浏览 0 评论

一、预备知识

大端模式、小端模式

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

网络字节序

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

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

#include <arpa/inet.h>

//这些函数调用成功后返回处理后的值,调用失败则返回-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 <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

//数字加点类型 转换成 将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 <arpa/inet.h>

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 <sys/socket.h>

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 <netinet/in.h>

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 <sys/types.h> 
#include <sys/socket.h>

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 <sys/types.h>
#include <sys/socket.h>

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 <sys/types.h>
#include <sys/socket.h>

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

accept

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

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 <sys/types.h> 
#include <sys/socket.h>

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

读写函数

<unistd.h>

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

关闭函数

<unistd.h>

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

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

服务器

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

#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 <stdio.h> 
#include <stdlib.h> 
#include <string.h>
#include <errno.h> 
#include <sys/types.h> 
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#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-常用操作命令介绍(linux常用的命令大全)

1.帮助命令帮助命令1.1help命令语法格式:命令--help作用:查看某个命令的帮助信息示例#ls--help#netstat--help1.2man命令语法格式:man命令...

推荐:一个小而美的Java工具类库(java工具软件)

前言是的,你没看错,没看错,它就是hutool!相信很多做java开发的朋友应该都已经认识并使用过它了,今天带大家再重温一下它都有哪些功能,并以示例来看看hutool是如何简便实现JWT认...

【SpringBoot后端开发】第三部分 Linux操作系统常用命令(3)

创作不易,请帮忙转发、点赞和评论!四、Linux常用命令对于Linux系统来说,中央处理器、内存、磁盘驱动器、键盘、鼠标、用户等都是文件,而Linux系统管理的命令是它正常运行的核心,与之DOS命令类...

linux常用命令在线查询工具(linux常用命令在线查询工具有哪些)

linuxvi编辑器常用命令linux查看iplinuxfind-name查找文件名linuxshelllinux查看端口占用linux删除文件命令linuxcp命令复制文件到另一个...

使用免费绿色工具chfs,将文件夹共享成网盘

需求:业务需求方有个需要将apk包上传到服务器中,通过chfs可以将服务器目录共享出来,可以可以登录后台自行上传apk文件包。本文就教大家三个知识点1.centos7下使用chfs,共享目录。2.使用...

Mysql和Hive之间通过Sqoop进行数据同步

文章回顾理论大数据框架原理简介大数据发展历程及技术选型实践搭建大数据运行环境之一搭建大数据运行环境之二本地MAC环境配置CPU数和内存大小查看CPU数sysctlmachdep.cpu#核数为...

真实案例记录Linux被植入rootkit导致服务器带宽跑满的解决过程

一、关于linux下的rootkitrootkit是Linux平台下最常见的一种木马后门工具,它主要通过替换系统文件来达到攻击和和隐蔽的目的,这种木马比普通木马后门更加危险和隐蔽,普通的检测工...

python周期任务调度工具Schedule使用详解

如果你想周期性地执行某个Python脚本,最出名的选择应该是Crontab脚本,但是Crontab具有以下缺点:不方便执行秒级任务。当需要执行的定时任务有上百个的时候,Crontab的管...

Linux 系统日常巡检脚本(shell巡检脚本)

Linux系统日常巡检脚本,巡检内容包含了,磁盘,内存cpu进程文件更改用户登录等一系列的操作直接用就行了。报告以邮件发送到邮箱在log下生成巡检报告。#!/bin/bash#@Au...

Schedule—简单实用的 Python 周期任务调度工具

如果你想周期性地执行某个Python脚本,最出名的选择应该是Crontab脚本,但是Crontab具有以下缺点:1.不方便执行秒级任务。2.当需要执行的定时任务有上百个的时候,Cronta...

celery定时与异步任务详解(定时任务异步执行)

celery简介Celery是一个简单、灵活且可靠的,处理大量消息的分布式系统,专注于实时处理的异步任务队列,同时也支持任务调度。Celery的架构由三部分组成,消息中间件(messagebroke...

开源免费的定时任务管理系统:Gocron

Gocron:精准调度未来,你的全能定时任务管理工具!-精选真开源,释放新价值。概览Gocron是github上一个开源免费的定时任务管理系统。它使用Go语言开发,是一个轻量级定时任务集中调度和管理...

PHP Laravel定时任务Schedule(laravel定时任务原理)

前提:本文方法是利用Linux的crontab定时任务来协助实现Laravel调度(Mac也一样)。一、首先添加Crontab定时任务,这里只做简单介绍:用命令crontab-e添加如下内容**...

Linux的常用命令就是记不住,怎么办?于是推出了这套教程

1.帮助命令1.1help命令#语法格式:命令--help#作用:查看某个命令的帮助信息#示例:#ls--help查看ls命令的帮助信息#netst...

如何定期执行 Python 脚本:5 种常见方法

定期执行任务是自动化工作流程中的重要环节,无论是数据抓取、文件备份,还是定期报告生成,定时运行脚本都可以极大提高效率。本文将介绍五种方法,通过这些方法,你可以轻松设置定期执行Python脚本的任务...

取消回复欢迎 发表评论: