linux 网络编程socket

news/2024/11/22 22:04:07/

前言

        socket(套接字)是linux下进程间通信的一种方式,通常使用C-S(客户端-服务端)的方式通信,它可以是同一主机下的不同进程间通信或者不同主机的进程通信。

        socket是夹在应用层和TCP/UDP协议层间的软件抽象,向应用层开发人员提供API接口,向下隐藏协议层的具体细节,大大方便了我们开发人员。很多平台都实现了BSD scoket标准scoket接口,增强了可移植性。

        在进行socket网络编程之前,有必要对计算机网络有个大概的了解,这里推荐一篇博文,链接如下:TCP/IP协议族之TCP、UDP协议详解

1. socket概述

1.1 表示方法

Socket=(IP地址:端口号),套接字的表示方法是点分十进制的lP地址后面加上端口号,中间用冒号或逗号隔开。每一个传输层连接唯一地被通信两端的两个端点所确定。

1.2 socket主要类型

 流套接字(SOCK_STREAM):用于提供面向连接、可靠的数据传输服务。该服务将保证数据能够实现无差错、无重复送,并按顺序接收。

 数据报套接字(SOCK_DGRAM):提供一种无连接的服务。该服务并不能保证数据传输的可靠性,数据有可能在传输过程中丢失或出现数据重复,且无法保证顺序地接收到数据。

 原始套接字(SOCK_RAW):原始套接字可以读写内核没有处理的IP数据包,而流套接字只能读取TCP协议的数据,数据报套接字只能读取UDP协议的数据。因此,如果要访问其他协议发送的数据必须使用原始套接。

2. socket-API接口

2.1 socket函数

socket函数用于初始化创建一个通信端点,调用成功返回一个socket描述符,类似于open函数的文件描述符;

可以使用close函数来关闭socket,释放占用的资源。

头文件:

#include <sys/types.h>

#include <sys/socket.h>

函数原型:

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

参数:

domain:指示通信协议族;

        AF_UNIX或 AF_LOCAL:Local communication本地通信;

        AF_INET:IPv4 Internet protocols,就是我们通常用的ipv4地址;

        AF_INET6:IPv6 Internet protocols,ipv6地址;

        AF_NETLINK:Kernel user interface device;

        其它;

type:指定套接字的类型;

        SOCK_STREAM:提供有序的、可靠的、双向的、基于连接的字节流,默认TCP协议;

        SOCK_DGRAM:固定长度的、无连接的、不可靠的报文传递,默认UDP协议;

        SOCK_RAW:允许应用程序访问网络层的原始数据包,原始套接字;

protocol:通常设置为0,表示为给定的通信域和套接字类型选择默认协议。

返回值:

成功:返回非负数socket描述符;

失败:返回 -1。

2.2  bind函数

bind函数通常用于服务端,将服务端的socket文件与网络中的进程地址(IP地址+端口号)绑定;

指向sockaddr_in的结构体指针也可以指向sockaddr的结构体,所以经常使用sockaddr_in替代。

头文件:

#include <sys/types.h>

#include <sys/socket.h>

函数原型:

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

参数:

sockfd:socket描述符;

addr:地址(ip地址+端口号);

addrlen:第二个参数addr长度;

返回值:

成功:返回 0;

失败:返回 -1。

struct sockaddr { sa_family_t sa_family; char sa_data[14]; 
}struct sockaddr_in {sa_family_t sin_family;  //协议族in_port_t sin_port;      //端口号struct in_addr sin_addr; // IP 地址unsigned char sin_zero[8]; 
};

2.3 listen函数

listen函数调用进入监听状态,通常用于服务端,服务端调用listen函数进入监听状态,等待客户端的连接请求。

参数backlog表明连接等待队列的上限,如果该服务端已经连接上一个客户端,之后其它客户端的连接请求将进入连接等待队列,同时该队列会有个上限值,不可能无限大。

头文件:

#include <sys/types.h>
#include <sys/socket.h>

函数原型:

int listen(int sockfd, int backlog)

参数:

backlog:连接等待队列上限值;

返回值:

成功:返回  0;

失败:返回 -1。

2.4 accept函数

accept()函数用于获取客户端的连接请求并建立连接,通常用于服务端;它是一个阻塞函数,如果没有客户端的连接请求,则会阻塞等待;accept()函数返回的套接字连接到调用connect()的客户端,服务端通过该套接字与客户端进行数据交互。

头文件:

#include <sys/types.h>
#include <sys/socket.h>

函数原型:

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

参数:

sockfd:socket()函数返回的描述符;

addr:struct sockaddr指针变量,是一个传出参数,用于存放客户端的连接信息,可设为NULL,表示不关心客户端信息;

addrlen:表明传出参数addr的字节长度;可设为NULL;

返回值:

成功:返回一个新的socket描述符;

失败:返回 -1 。

2.5 connect函数

通常用于客户端,调用connect函数请求与服务端连接;常说的三次握手就是由connect触发的。

函数原型:

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

参数:

sockfd:调用socket生成的描述符;

addr:指定服务端的IP地址以及端口号等信息;

addrlen:表明addr信息长度;

返回值:

成功:返回 0;

失败:返回 -1。

2.6 发送函数

2.6.1 send函数

send函数通常用于TCP数据包的发送。

send()成功返回只能表示数据包已经发送出去了,并不能代表接收端已经接收到数据了。

函数原型:

ssize_t send(int sockfd, const void *buf, size_t len, int flags)

参数:

sockfd:socket函数返回值;

buf:发送的数据缓冲区;

len:发送的数据大小;

flags:标志位,通常设为0;

        MSG_DONTWAIT:允许非阻塞操作;

        MSG_MORE:延迟发送数据包允许写更多数据;

        其它......

返回值:

失败:返回 -1;

2.6.2 sendto函数

常用于UDP数据发送,因为UDP是无连接的,因此需要指定目的地址。

函数原型:

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                      const struct sockaddr *dest_addr, socklen_t addrlen)

参数:

dest_addr:数据发送的目的地址;

addrlen:目的地址长度;

2.7 接收函数

2.7.1 recv函数

用于TCP数据的接收;阻塞模式下,直到有数据才返回。

函数原型:

ssize_t recv(int sockfd, void *buf, size_t len, int flags)

参数:

buf:数据接收缓冲区;

flags:一般设为0;

        MSG_DONTWAIT:启动非阻塞操作;

        MSG_PEEK:返回数据包内容而不真正取走数据包;

        其它......

返回值:

成功:返回接收的数据长度;

失败:返回 -1。

2.7.2 recvfrom函数

一般用于UDP数据的接收。

函数原型:

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                        struct sockaddr *src_addr, socklen_t *addrlen)

参数:

src_addr:指向数据源端地址。

2.8 IP地址格式转化

2.8.1 inet_ntop函数

用于二进制形式字符串转为十进制形式字符串。

头文件:

#include <arpa/inet.h>

函数原型:

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

参数:

af:为AF_INET(ipv4)或AF_INET6(ipv6);

src:需要转化的源字符串;一般为struct in_addr结构体的对象;

dst:转换生成的字符串存放的缓冲区;

size:缓冲区的大小;

返回值:

成功:返回指向dst的指针;

失败:返回NULL。

代码示例:

#include <stdio.h>
#include <arpa/inet.h>int main()
{struct in_addr src_addr;char dst_addr[32];src_addr.s_addr = 0x6401a8c0;inet_ntop(AF_INET,&src_addr,dst_addr,sizeof(dst_addr));printf("dst addr:%s\n",dst_addr);return 0;
}

2.8.2 inet_pton函数

用于十进制形式字符串转为二进制形式字符串,返回值为大端存储。

头文件:

#include <arpa/inet.h>

函数原型:

int inet_pton(int af, const char *src, void *dst)

返回值:

成功:返回1;

格式无效:返回0;

失败:返回-1。

struct in_addr {__be32  s_addr;
};

示例代码:

#include <stdio.h>
#include <arpa/inet.h>#define IP_ADDR "192.168.1.100"
int main()
{struct in_addr dst_addr;inet_pton(AF_INET,IP_ADDR,&dst_addr);printf("dst addr:%x\n",dst_addr.s_addr);return 0;
}

2.9 存储顺序

host:主机字节序(小端存储);

network:网络字节序(大端存储)。

2.9.1 主机转网络存储顺序

头文件:

#include <arpa/inet.h>

函数原型:

uint32_t htonl(uint32_t hostlong);        //32位IP地址

uint16_t htons(uint16_t hostshort);      //16位端口号

INADDR_ANY指定地址为0.0.0.0地址,表示监听所有的IP地址

2.9.2 网络转主机存储顺序

uint32_t ntohl(uint32_t netlong);

uint16_t ntohs(uint16_t netshort);

3.  socket工作流程

3.1  TCP工作流程

3.2 UDP工作流程

4. TCP通信测试代码

4.1 服务端

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>#define PORT 6666int main()
{int socket_fd,fd;int ret;char send_buff[64] = "Data was send by server.";    //连接上发给客户端的数据char recv_buff[64];char client_ip[32];struct sockaddr_in client_msg;int msg_len = sizeof(client_msg);struct sockaddr_in server_addr;server_addr.sin_family = AF_INET;server_addr.sin_port = htons(PORT);server_addr.sin_addr.s_addr = htonl(INADDR_ANY);socket_fd = socket(AF_INET,SOCK_STREAM,0);if(socket_fd < 0){perror("socket");exit(0);}ret = bind(socket_fd,(struct sockaddr *)&server_addr,sizeof(server_addr));if(ret == -1){perror("bind");close(socket_fd);exit(0);}ret = listen(socket_fd,32);if(ret == -1){perror("listen");close(socket_fd);exit(0);}printf("listen...\n");fd = accept(socket_fd,(struct sockaddr *)&client_msg,&msg_len);inet_ntop(AF_INET,&client_msg.sin_addr.s_addr,client_ip,sizeof(client_ip));printf("client ip addr:%s,port:%d\n",client_ip,client_msg.sin_port);send(fd,send_buff,sizeof(send_buff),0);while(1){memset(recv_buff,0,sizeof(recv_buff));recv(fd,recv_buff,sizeof(recv_buff),0);    //阻塞接收客户端的数据printf("recv buff :%s\n",recv_buff);if(strncmp("quit",recv_buff,4) == 0){printf("socket closer.\n");close(fd);close(socket_fd);exit(0);}}return 0;
}

4.2 客户端

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>int main(int argc,char *argv[])
{int socket_fd;int ret;char send_buff[] = "From client datas.";char recv_buff[64];struct sockaddr_in client_msg;if(argc < 3){exit(0);}printf("ip:%s,port:%s\n",argv[1],argv[2]);client_msg.sin_family = AF_INET;inet_pton(AF_INET, argv[1], &client_msg.sin_addr);    //传入的ip地址client_msg.sin_port = htons(atoi(argv[2]));    //传入的端口号htonl(client_msg.sin_addr.s_addr);socket_fd = socket(AF_INET,SOCK_STREAM,0);if(socket_fd == -1){perror("socket");exit(0);}printf("socket_fd :%d\n",socket_fd);ret = connect(socket_fd,(struct sockaddr *)&client_msg,sizeof(client_msg));  //发起连接if(ret == -1){perror("connect");close(socket_fd);exit(0);}recv(socket_fd,recv_buff,sizeof(recv_buff),0);printf("recv buff : %s\n",recv_buff);while(1){memset(send_buff,0,sizeof(send_buff));printf("client data input: ");fgets(send_buff,sizeof(send_buff),stdin);    //从键盘输入数据信息send(socket_fd,send_buff,sizeof(send_buff),0);if(strncmp("quit",send_buff,4) == 0){close(socket_fd);exit(0);}}return 0;
}

4.3 测试结果

在Ubuntu下打开两个shell终端,分别运行服务端程序和客户端程序。

客户端传入服务端ip地址和端口号的参数,然后向服务端发送数据信息测试,结果如下图。


http://www.ppmy.cn/news/19949.html

相关文章

MyBatis(一)MyBatis概述

一、什么是框架 ● 在文献中看到的framework被翻译为框架 ● java常用的框架&#xff1a; SSM三大框架&#xff1a;Sping SpringMVC MyBatisSpringBootSpringCloud● 框架其实就是对通用代码的封装&#xff0c;提前写好了一堆接口和类&#xff0c;我们可以在做项目的时候直…

SNARK+深度神经网络

1. 引言 SNARK深度神经网络&#xff0c;相关开源实现有&#xff1a; 1&#xff09;Ezkl&#xff08;Rust&#xff09;&#xff1a;借助Halo2证明系统&#xff0c;实现了50层的MobileNetV2的执行证明。具体见Daniel Kang等人2022年论文Scaling up Trustless DNN Inference with…

2023年web类第一期总结

&#x1f340;本人简介&#xff1a; 吉师大一最爱逃课的网安混子、 华为云享专家、阿里云专家博主、腾讯云自媒体分享计划博主、 华为MindSpore优秀开发者、迷雾安全团队核心成员&#xff0c;CSDN2022年运维与安全领域第15名 &#x1f341;本人制作小程序以及资源分享地址&am…

【Linux】六、Linux 基础IO(四)|动态库和静态库

目录 十一、动态库和静态库 11.1 动态库和静态库定义 11.2 动静态库的基本原理 11.3 静态库的打包与使用 11.3.1 静态库的打包 11.3.2 静态库的使用 11.4 动态库的打包与使用 11.4.1 动态库的打包 11.4.2 动态库的使用 11.5 动态库的加载 十一、动态库和静态库 11.1…

指针与数组

目录指针运算&#xff08;补&#xff09;指针指针指针的关系运算&#xff08;补&#xff09;指针与数组数组名二级指针指针数组指针运算&#xff08;补&#xff09; 指针指针 上一篇博客我们介绍了指针运算中的三种常见运算&#xff1a;指针整数&#xff0c;指针关系运算&…

JavaEE10-Spring Boot配置文件

目录 1.配置文件作用 2.配置文件的格式 为配置文件安装提示插件 2.1. .properties&#xff08;旧版&#xff0c;默认的&#xff09; 2.1.1.基本语法 PS:配置文件中使用"#"来添加注释信息&#xff0c;2种添加方式&#xff1a; 2.1.2.缺点分析 2.2. .yml&#…

1.设计模式的前奏

哪些维度评判代码质量的好坏&#xff1f; 常用的评价标准 可维护性&#xff08;maintainability&#xff09;:维护代码的成本可读性&#xff08;readability&#xff09;可扩展性&#xff08;extensibility&#xff09;&#xff1a;码应对未来需求变化的能力灵活性&#xff0…

11.Java方法的综合练习题大全-双色球彩票系统,数字的加密和解密等试题

本篇文章是Java方法的专题练习,从第五题开始难度增大,涉及大厂真题,前四道题目是基础练习,友友们可有目的性的选择学习&#x1f618;&#x1f495; 文章目录前言一、数组的遍历1.注意点:输出语句的用法2.题目正解二、数组最大值三、判断是否存在四、复制数组五、案例一:卖飞机票…