Linux C简单服务器模型解析及完整代码

news2024/9/28 11:32:47

Linux C简单服务器模型解析及完整代码

  • 1.服务器端流程及解析
  • 2.服务器端代码
  • 3.客户端流程及解析
  • 4.客户端代码
  • 5.不足之处

在这里插入图片描述
(注:流程解析可结合相应代码处一起理解)

1.服务器端流程及解析

服务器端做的事情:
1.创建监听的套接字
int socket(int domain, int type, int protocol)

  • domain:协议族,可用AF_INET(代表IPv4),AF_INET6(代表IPv6),还有本地的通信AF_LOCAL(进程间通信),常用AF_INET。
  • type:使用的协议类型,是流式协议还是报式协议。对应的参数为SOCK_STREAM或SOCK_DGRAM。
  • protocol:使用的具体协议。一般填0,如果是流式协议默认使用TCP,如果是报式协议默认使用UDP。
  • 返回:创建成功则返回套接字的文件描述符。创建失败返回-1.

2.绑定。将监听所用的套接字绑定相关IP端口信息。
这里存储IP和端口的结构体有专用地址和通用地址,在我们自己构建的时候常常使用的是专用地址,而在具体使用的时候都要转化为通用地址。比如这里的专用地址有sockaddr_in (代表IPv4),sockaddr_in6(代表IPv6),sockaddr_un(Unix本地协议族)。然后通用地址为sockaddr。然后我们非常常用的就是一个sockaddr_in结构体,以IPv4为例。然后有不懂的结构体可以直接在Linux系统中查看man文档(比如man 2 socket, man 2 bind),并且有这些专门的结构体定义及所用的头文件。

struct sockaddr_in
  {
    sa_family_t sin_family;		/* __SOCKADDR_COMMON (sin_) */
    in_port_t sin_port;			/* Port number.  */
    struct in_addr sin_addr;		/* Internet address.  */

    /* Pad to size of `struct sockaddr'.  */
    unsigned char sin_zero[sizeof (struct sockaddr) -
			   __SOCKADDR_COMMON_SIZE -
			   sizeof (in_port_t) -
			   sizeof (struct in_addr)];
  };
 struct in_addr
  {
    in_addr_t s_addr;
  };

对于sockaddr_in来说,也就是IPv4地址,要记录三部分内容,第一个是协议族,常用的也就是前面提到的AF_INET,第二部分为接受的IP地址,当选用INADDR_ANY时意味着接受任意的IP地址,第三个为使用的端口,htons代表着host to net short,也就是unsigned short的主机字节序到网络字节序的转换,两个字节的转换,端口号恰好可以用两个字节16位的大小来存储。同理ntohl代表的是net to host long,也就是long类型的数据从网络字节序转换到主机字节序,至于long类型,恰好对应的是IP地址32位,四个字节。至于上面的第四个参数,是用于结构体内存对齐的,就不用管了。
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

  • sockfd:需要绑定的套接字。这里也就是第一步创建的套接字。
  • addr:注意看它的类型,这里是通用地址指针,而我们之前使用的都是sockaddr_in(IPv4专用地址),故这里的话需要做一个类型转换,直接类型强转即可。
  • addrlen:代表的是地址长度。
  • 返回:如果绑定成功返回0。如果绑定失败则发挥-1,并且会设置错误号。

3.监听。这里的绑定是监听所绑定的套接字。
int listen(int sockfd, int backlog);

  • sockfd:监听套接字,这里也就是前面初始套接字与IP端口绑定好的有完整内容的套接字。
  • backlog:设置最大连接数。一般设置为5即可,因为很多时候使用了服务器资源又会马上释放了。
  • 返回:如果绑定成功返回0。如果绑定失败则发挥-1,并且会设置错误号。

4.接受客户端连接。接受连接所用的函数是accept,如果没有客户端连接进入,那么就不会执行下面的代码,也就是不会执行下面的具体通信,是阻塞在那的。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

  • sockfd:放入被监听的套接字。
  • addr:放入一个客户端信息结构体,主要存放的是IP端口这些信息,这里同样可以用sockaddr_in结构体,只需转换一下即可,注意这里可用一个传参,之后如果想使用客户端的信息的时候就非常方便。
  • addrlen:客户端结构体的大小,直接sizeof即可。
  • 返回:接受客户端连接成功返回一个新的套接字(文件描述符),接受连接失败返回-1并且设置错误号。

5.通信(通常情况:先recv后send)
服务器端通常的一个通信流程通常都是先接收到客户端请求的信息,然后再发送对应的消息给客户端。
ssize_t recv(int sockfd, void *buf, size_t len, int flags);

  • sockfd:通信所用的套接字,是accept接受后产生的套接字,而不是监听的套接字,注意区分。
  • buf:接收数据所用的缓冲区,这里可以用字符数组,然后做一个强制类型转换。
  • len:代表的是接收到的数据长度。
  • flags:一般不用管。
  • 返回:为0,对方断开连接。为>0,则代表接收到了数据,可进一步处理。为-1,代表出错,并且设置错误号。

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

  • sockfd:通信所用的套接字,是accept接受后产生的套接字,而不是监听的套接字,注意区分。
  • buf:需要发送给客户端的数据缓冲区,和前面的接收缓冲区相区分,同样也可做一个类型转换。char *转换为void *。
  • len:发送出去的数据长度。
  • flags:一般不用管。

6.释放资源
主要是监听所用的套接字和连接所用的套接字。

2.服务器端代码

//创建TCP服务器端
//时间:2022年3月26日15:30:18
//作者:credic_1017
//sever.c

#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

int main(){
	//1.创建socket(用于监听的套接字)
	int lfd = socket(AF_INET, SOCK_STREAM, 0);

	if ( lfd == -1 ){
		perror("socket");
		exit(-1);
	}

	//2.绑定
	struct sockaddr_in saddr;
	saddr.sin_family = AF_INET;
	//inet_pton(AF_INET, "192.168.204.128",saddr.sin_addr.s_addr);
	saddr.sin_addr.s_addr = INADDR_ANY;
	saddr.sin_port = htons(9999);
	int ret = bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr));
	if ( ret == -1 ){
		perror("bind");
		exit(-1);
	}

	//3.监听
	ret = listen(lfd, 8);
	if ( ret == -1 ){
		perror("listen");
		exit(-1);
	}

	//4.接收客户端连接
	struct sockaddr_in clientaddr;
	socklen_t len = sizeof(clientaddr);
	int cfd = accept(lfd, (struct sockaddr *)&clientaddr, &len);
	if( cfd == -1 ){
		perror("accept");
		exit(-1);
	}

	//输出客户端信息
	char clientIP[16];
	inet_ntop(AF_INET, &clientaddr.sin_addr.s_addr, clientIP, sizeof(clientIP));
	unsigned short clientPort = ntohs(clientaddr.sin_port);
	printf("client ip is: %s, port is %d\n", clientIP, clientPort);

	//5.获取客户端信息
	char recvBuf[1024] = {0};
	char data[1024] = {0};
	while(1){		
		int recvlen = read(cfd, recvBuf, sizeof(recvBuf));
		sleep(1);
		if( recvlen == -1 ){
			perror("read");
			exit(-1);
		}else if(recvlen > 0){
			printf("rece client data: %s\n", recvBuf);
		}else if(recvlen == 0){
			//表示客户端断开连接
			printf("client closed\n");
		}
		//给客户端发送数据
		fgets(data, 1024 , stdin);
	    data[1023] = '\0';
		write(cfd, data, strlen(data));
	}

	//关闭文件描述符
	close(lfd);
	close(cfd);

	return 0;
}

3.客户端流程及解析

1.创建套接字
2.连接服务器端
首先创建服务器套接字,然后填写服务器IP端口,注意IP和端口都得做一个本地字节序到网络字节序的转换。这里端口进行字节序转换使用的函数是htons,而IP使用的是inet_pton,主要是由于IP地址常用点分十进制的记法,而端口则直接是一个整数,如果是一个大整数则可以用htonl函数,具体含义在前面的服务器端解析已经说过了。
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

  • sockfd:连接所用的套接字。
  • addr:需要进行连接的服务器端存储结构体。
  • addrlen:服务器信息存储结构体大小。
  • 返回:连接成功,返回0。连接失败返回-1并且设置错误号。

3.通信(通常先send再recv)
作为客户端通常先发送消息给服务器端,之后再接收来自服务器端的消息。具体流程和上面服务器端通信类似。
4.断开连接释放资源
通常而言是客户端断开连接的,当然服务器端先断开连接也是可以的。释放资源主要是释放套接字(文件描述符)。

4.客户端代码

//创建TCP客户端
//时间:2022年3月27日15:13:07
//作者:credic_1017
//client.c

#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

int main(){
    //1.创建套接字
    int fd = socket(AF_INET, SOCK_STREAM, 0);

    //2.连接服务器端
    struct sockaddr_in severaddr;
    severaddr.sin_family = AF_INET;
    inet_pton(AF_INET, "192.168.204.128", &severaddr.sin_addr.s_addr);
    severaddr.sin_port = htons(9999);
    int ret = connect(fd, (struct sockaddr *)&severaddr, sizeof(severaddr));
    if (ret == -1){
        perror("connect");
        exit(-1);
    }

    //3.通信
    char recvBuf[1024] = {0};
    char data[1024] = {0};
    while(1){
        fgets(data, 1024 , stdin);
        data[1023] = '\0';
        write(fd, data, strlen(data));

        sleep(1);
        int len = read(fd, recvBuf, sizeof(recvBuf));
        if( len == -1 ){
            perror("read");
            exit(-1);
        }else if(len > 0){
            printf("rece sever data: %s\n", recvBuf);
        }else if(len == 0){
            //表示客户端断开连接
            printf("sever closed...\n");
        }
    }   
    close(fd);
    return 0;
}

5.不足之处

目前的通信是一对一的,单个进程对单个进程,也就是上面的通信服务器端也面对两个及两个以上客户端的连接是无法进行处理的。在两个连接的情况下,一个客户端资源已经释放,第二个客户端依旧不能与服务器端进行通信。这是一个很大的弊端,还在学习多线程及多进程进行服务器端进行高并发,能够较好的解决这个问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/55740.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

JAVA培训之数据库表关联关系

数据库表的关联关系有如下三种&#xff1a; 一对多 一对一 多对多 一对多的关系比较常见&#xff0c;我们在之前练习的都是一对多的关系操作&#xff0c;因此这里就不再重复介绍了。 1.一对一关系 在现实生活中&#xff0c;丈夫与妻子之间的关系就是一对一的关系&…

参数估计(1)-参数的点估计

为了考察总体X 的分布&#xff0c;我们将在2.4 节给出经验分布函数&#xff0c;并在总体X为连续型情形下绘制了频率直方图从中可大致看出总体的分布情况或分布形式&#xff0c;但总体分布中包含的参数往往不知&#xff0e;如何根据抽样对未知参数进行估计&#xff0c;就是本章讨…

pandas分组与聚合groupby()函数详解

一、groupby分组与聚合 分组与聚合通常是分析数据的一种方式&#xff0c;通常与一些统计函数一起使用&#xff0c;查看数据的分组情况 DataFrame.groupby(byNone, axis0, levelNone, as_indexTrue, sortTrue, group_keys_NoDefault.no_default, squeeze_NoDefault.no_default…

基于docker部署实现接口自动化持续集成

01 jenkins介绍及docker部署 Jenkins是一个开源软件项目&#xff0c;是基于Java开发的一种持续集成工具&#xff0c;用于监控持续重复的工作&#xff0c;旨在提供一个开放易用的软件平台&#xff0c;使软件项目可以进行持续集成 1、创建网络 2、下载并运行 docker:dind镜像…

思科路由器地址转换配置

思科路由器地址转换1.端口复用(PAT)2.配置PAT1.端口复用(PAT) 复用地址转换也称为端口地址转换&#xff08;Port Address Translation&#xff0c;PAT&#xff09;&#xff0c;首先是一种动态地址转换。路由器将通过记录地址、应用程序端口等唯一标识一个转换。通过这种转换&am…

Linux 负载均衡介绍

Linux 负载均衡介绍 LB LB&#xff1a;Load Balancing&#xff0c;负载均衡&#xff08;增加处理能力&#xff09;,有高可用能力&#xff0c;但不是高可用集群负载均衡集群&#xff0c;是以提高服务的并发处理能力为根本着眼点 负载均衡设备分类 1.软件负载均衡设备 (1)LVS&…

肝病患者关爱与病例管理

国内的肝病患者人数多达3亿人&#xff0c;并且还在逐年增多。 为慢性肝炎患者提供患者教育、病情自我管理、病友交流、医患交流等一系列的关怀行动&#xff0c;为医生提供病例数据管理系统平台推出了“肝病患者关爱与病例管理系统" 用户需求 平台覆盖用户群 中国肝病患者…

Thumbs.db文件取消自动生成及删除方法

一、背景 公司有一台centos服务器作为某个部门内部资料共享用,部署了samba服务。 每个用户具有自己的账户(账户不共用); 用户基本都是通过windows电脑访问samba共享文件; 最近有用户需要删除个别文件夹,发现不能删除Thumbs.db,导致文件夹也不能删除! 二、Thumbs.db是…

Java数据类型详细介绍

Java程序中要求参与的计算的数据&#xff0c;必须要保证数据类型的一致性&#xff0c;如果数据类型不一致将发生类型的转换。本文将通过示例详细说说Java中数据类型的转换&#xff0c;感兴趣的可以了解一下 1. Java数据类型 前面说到&#xff0c;程序运行时产生的临时数据我们…

继北极星项目后,又一款低成本AR眼镜开源方案:OpenAR

2018年的时候&#xff0c;Leap Motion&#xff08;Ultraleap前身&#xff09;曾开源了一款低成本、简易的AR头显方案&#xff1a;Project North Star&#xff08;北极星项目&#xff09;。该方案主要用来帮助爱好者、开发者们快速上手AR开发&#xff0c;而无需购买HoloLens等昂…

Python_文件操作

目录 一、文件编码 二、什么是文件 三、文件操作 1、打开文件 open()打开函数 2、读取文件 3、写入文件 f.write() 使用w模式 4、文件追加 类比文件写入&#xff0c;只是将‘w’-->‘a’ 5、文件操作综合案例 一、文件编码 Thinking: 计算机只能识别 0和1,那么文本文件 如何…

Delaunay三角网之逐点插入法(优化版本三)

文章目录 一、简介二、实现代码三、实现效果参考文献一、简介 继续对三角网的研究,这一个版本的三角网构网思路很是巧妙,虽然仍是基于点的插入算法,但已经有些分治算法的影子,构网速度相较于前面两个版本要快很多,12万个点可以在1s内完成构网。具体的构网过程如下所述: 1…

查询运行在顶部的app包名及类名

android的ActivityManager会在logcat中打印出当前正在显示的app的包名以及类名&#xff0c;注&#xff0c;这是由ActivityManager打印&#xff0c;使用的日志TAG就是ActivityManager&#xff0c;所以我们在过虑信息时不能以自己程序的进程进行过滤&#xff0c;使用ActivityMana…

GBase 8s 产品功能-高可用和ER

GBase 8s 提供了业界领先的高可用集群技术 HAC&#xff0c; SSC&#xff0c; ER&#xff0c; 提供秒级故障 自动切换功能&#xff0c; 使数据库的可用性达到 99.999%&#xff0c; 具备如下特点&#xff1a; 通过不同方案组合&#xff0c; GBase 8s 具有搭建两地三中心、 同城双…

行业内口碑好值得信赖的短网址,让你不再选择恐惧

转眼一年又过去了&#xff0c;最近发现网络上有各种各样的短网址平台&#xff0c;让人眼花缭乱&#xff0c;都声称免费并且功能强大&#xff0c;但是据我的了解&#xff0c;很多免费的短网址都是有使用上的限制的&#xff0c;比如生成条数、访问次数、有广告等等、还有各种各样…

项目中的奇葩需求你都怎么应对?

XX群聊&#xff1a;“项目中的奇葩需求怎么应对&#xff1f;”&#xff08;项目三兄弟加入了群聊&#xff09; 小赵&#xff1a;“无论是啥需求&#xff0c;我都是甲方爸爸至上&#xff0c;尽力满足需求、让客户满意。但每个项目初期我都跟客户处得特别好&#xff0c;项目中期…

音视频同步原理

音视频同步 更多精彩内容 音频系统概述 音频时延payload_delay_ms jitter_delay(neteq)。 在WebRTC中有neteq&#xff0c;所以&#xff0c;音频的卡顿以及卡顿时长都是放在neteq内部进行计算的。 时间戳 时间戳的概念主要有以下几个&#xff1a; ntp时间戳: NTP时间戳是绝…

SPI协议讲解与总结

1.SPI通讯协议 SPI是串行外设接口&#xff08;Serial Peripheral Interface&#xff09;的缩写&#xff0c;是一种高速的&#xff0c;全双工&#xff0c;同步的通信总线 1.1 SPI引脚与工作过程 SPI的通信原理很简单&#xff0c;它以主从方式工作&#xff0c;这种模式通常有一…

GitHub推送代码时弹出验证框总是登录失败

最近同事准备用GitHub布置个人博客&#xff0c;但是提交代码时总是弹出登录框然后输入我GitHub的账号密码后一直提示报错Logon failed, use ctrlc to cancel basic credential prompt&#xff0c;然后我才知道还有人不知道这个问题 2021年8月13日, github不再支持用密码提交代码…

【Mysql】内外连接

文章目录**1.内连接****2.外连接****3.小结**1.内连接 内连接实际上就是利用where子句对两种表形成的笛卡儿积进行筛选,我们前面学习的查询都是内连接,也是在开发过程中使用的最多的连接查询 select 字段 from 表1 inner join 表2 on 连接条件 and 其他条件&#xff1b;案例:…