Linux下TCP/IP网络编程示例——实现服务器/客户端通信(一)

2023-05-16

一、说明

最近梳理网络编程的一些知识点时,整理了一些笔记,写了一些demo例程,主要包含下面几部分,后面会陆续完成。

1、Linux下TCP/IP网络编程示例——实现服务器/客户端通信(一):基于socket实现基本的服务器与客户端通信,不支持多并发,即只支持与一个客户端通信。
2、Linux下TCP/IP网络编程示例——实现服务器/客户端通信(二):基于socket实现基本的服务器与客户端通信,不使用多进程/多线程和多路复用,实现服务端多并发功能。
3、Linux下TCP/IP网络编程示例——实现服务器/客户端通信(三):基于socket实现基本的服务器与客户端通信,使用多线程实现服务端的多并发功能。
4、Linux下TCP/IP网络编程示例——实现服务器/客户端通信(四):基于socket实现基本的服务器与客户端通信,使用多路复用(select)实现服务端的多并发功能。
5、Linux下TCP/IP网络编程示例——实现服务器/客户端通信(五):基于socket实现基本的服务器与客户端通信,使用多路复用(poll)实现服务端的多并发功能。
6、Linux下TCP/IP网络编程示例——实现服务器/客户端通信(六):基于socket实现基本的服务器与客户端通信,使用多路复用(epoll)实现服务端的多并发功能。

二、正文

1、TCP网络编程模型

2、网络编程API
(1)socket()
 函数原型: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网络通信;
    type:套接字类型,常用选项如下:
        SOCK_STREAM         :流式套接字,唯一对应于TCP;
        SOCK_DGRAM         :数据报套接字,唯一对应于UDP;
        SOCK_RAW              :原始(透传)套接字;
    protocol:
        通常填0,在type类型为SOCK_RAW时,需要该参数。
 返回值:成功时返回套接字(文件描述符),失败返回-1。

(2)bind()
 函数原型:int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
 函数作用:绑定服务器相关信息;
 参数说明:
    sockfd:通过socket()得到的文件描述符;
    addr  :指向struct sockaddr类型结构体变量的指针,包含了IP地址和端口号;实际使用时,如果是网络编程,一般都是定义struct sockaddr_in类型的变量,然后取该变量的地址强转为struct sockaddr*类型;
    addrlen:struct sockaddr类型结构体变量所占内存空间大小;
 返回值:成功时返回0,失败返回-1。
 补充说明:bind()函数中用的是是通用结构体,实际使用中,用于网络编程和本地进程/线程间通信时,一般都不用通用结构体而是用struct sockaddr_in类型和struct sockaddr_un类型的结构体变量。这三种结构体成员如下:
 通用地址结构:
    struct sockaddr
    {    
        u_short  sa_family;    // 地址族, AF_xx 
        char  sa_data[14];     // 14字节协议地址
    };

 Internet协议地址结构:
    struct sockaddr_in 
    {       
        u_short sin_family;      // 地址族, AF_INET,2 bytes    
        u_short sin_port;        // 端口,2 bytes  
        struct in_addr sin_addr; // IPV4地址,4 bytes      
        char sin_zero[8];        // 8 bytes unused,作为填充
    }; 
本地通信协议地址结构:
    struct sockaddr_un
    {
        sa_family_t sun_family; //协议族
        char sun_path[108];     //套接字文件路径
    }

(3)listen()
 函数原型:int listen(int sockfd, int backlog);
 函数作用:将主动套接字变为被动套接字;
 参数说明:
    sockfd:通过socket()得到的文件描述符;
    backlog:指定了正在等待连接的最大队列长度,它的作用在于处理可能同时出现的几个连接请求;
    返回值:成功时返回0,失败返回-1。
(4)accept()
 函数原型:int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
 函数作用:阻塞等待客户端的连接请求,当由客户端请求连接时,该函数返回已经建立连接的新套接字(文件描述符),用于客户端和服务器通信;
 参数说明:
    sockfd:通过socket()得到的文件描述符;
    addr  :指向struct sockaddr类型结构体变量的指针;用于存放连接过来的客户端的IP地址和端口号,实际使用时,如果不需要客户端的信息,可以直接填NULL;
    addrlen:指向socklen_t类型变量的指针;表示struct sockaddr类型结构体变量所占内存空间大小;
 返回值:成功时返回新的文件描述符,后面与客户端通信用的就是该返回的文件描述符,失败返回-1。
(5)connect()
 函数原型:int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
 函数作用:客户端调用该函数向服务器发起连接请求;
 参数说明:
    sockfd:通过socket()得到的文件描述符;
    addr  :指向struct sockaddr类型结构体变量的指针,用于填充服务端的IP与端口信息;
    addrlen:struct sockaddr类型结构体变量所占内存空间大小;
 返回值:成功时返回0,失败返回-1。

3、实现简单的服务器/客户端通信例程
例程说明:
下面例程实现了基本的CS通信模型。服务器启动后,等待客户端的连接,如果有客户端连接过来,则打印客户端的IP与端口信息,并接收打印客户端发送过来的消息。但是仅支持一对一的通信连接,即只能与一个客户端进行连接与通信,不支持多并发。

客户端例程:client.c

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

#define SERVER_PORT		6000    //
#define SERVER_IP		"192.168.99.112"	//服务器IP地址

int main(int argc, const char *argv[])
{
	int connect_fd = -1;
	struct sockaddr_in server;
	socklen_t saddrlen = sizeof(server);

	memset(&server, 0, sizeof(server));
	
	connect_fd = socket(AF_INET, SOCK_STREAM, 0);
	if (connect_fd < 0)
	{
		printf("socket error!\n");
		return -1;
	}

	server.sin_family = AF_INET;
	server.sin_port = htons(SERVER_PORT);
	server.sin_addr.s_addr = inet_addr(SERVER_IP);

	if (connect(connect_fd, (struct sockaddr *)&server, saddrlen) < 0)
	{
		printf("connect failed!\n");
		return -1;
	}

	char buf[256] = {0};
	while (1)
	{
		printf(">");
		fgets(buf, sizeof(buf), stdin);
		if (strcmp(buf, "quit\n") == 0)
		{
			printf("client will quit!\n");
			break;
		}
		write(connect_fd, buf, sizeof(buf));
	}
	close(connect_fd);

	return 0;
}

服务端例程:server.c

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

#define SERVER_PORT		6000
#define SERVER_IP		"192.168.99.112"

int listen_fd = -1;

void signal_handler(int arg)
{
	printf("close listen_fd(signal = %d)\n", arg);
	close(listen_fd);
	exit(0);
}

int main(int argc, const char *argv[])
{
	int new_fd  = -1;
	struct sockaddr_in server;
	struct sockaddr_in client;
	socklen_t saddrlen = sizeof(server);
	socklen_t caddrlen = sizeof(client);

	signal(SIGINT, signal_handler);

	memset(&server, 0, sizeof(server));
	memset(&client, 0, sizeof(client));

	listen_fd = socket(AF_INET, SOCK_STREAM, 0);
	if (listen_fd < 0)
	{
		printf("socket error!\n");
		return -1;
	}

	server.sin_family = AF_INET;
	server.sin_port = htons(SERVER_PORT);
	server.sin_addr.s_addr = inet_addr(SERVER_IP);

	if (bind(listen_fd, (struct sockaddr *)&server, saddrlen) < 0)
	{
		printf("bind error!\n");
		return -1;
	}

	if (listen(listen_fd, 5) < 0)
	{
		printf("listen error!\n");
		return -1;
	}

	char rbuf[256] = {0};
	int read_size = 0;
	while (1)
	{
		/*
		socket()创建的套接字默认是阻塞的,所以accept()在该套接字上进行监听时,
		如果没有客户端连接请求过来,accept()函数会一直阻塞等待;换句话说,程序
		就停在accept()函数这里,不会继续往下执行,直到有新的连接请求发送过来,唤醒accept()。
		*/
		new_fd = accept(listen_fd, (struct sockaddr *)&client, &caddrlen);
		if (new_fd < 0)
		{
			perror("accept");
			return -1;
		}

		printf("new client connected.IP:%s,port:%u\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
		while (1)
		{
			read_size = read(new_fd, rbuf, sizeof(rbuf));
			if (read_size < 0)
			{
				printf("read error!\n");
				continue;
			}
			else if (read_size == 0)
			{
				printf("client (%d) is closed!\n", new_fd);
				close(new_fd);
				break;
			}

			printf("recv:%s\n", rbuf);
		}
	}

	close(listen_fd);

	return 0;
}

关于server.c代码的说明:
1、为什么要将read()接收处理代码放到另一层while循环中,而不是和accept()放在同一层while循环中?

将接收客户端消息处理的代码放到单独的while循环中,是因为如果将read接收处理代码与accept放到同一层while循环中,会导致客户端在发送一次消息给服务器后,就没法再发送了。假如我们将上面read()和accept()相关部分代码写成下面这样,然后来分析一下代码的执行;

while (1)
{
	new_fd = accept(listen_fd, (struct sockaddr *)&client, &caddrlen);
	if (new_fd < 0)
	{
		perror("accept");
		return -1;
	}

	printf("new client connected.IP:%s,port:%u\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
	
	read_size = read(new_fd, rbuf, sizeof(rbuf));
	if (read_size < 0)
	{
		printf("read error!\n");
		continue;
	}
	else if (read_size == 0)
	{
		printf("client (%d) has been closed!\n", new_fd);
		close(new_fd);
		break;
	}

	printf("recv:%s\n", rbuf);
}

假如我们现在打开了一个客户端,服务器端accept()检测到该客户端连接请求,然后成功建立连接并返回了一个新的文件描述符;代码执行到read()部分,阻塞等待,因为客户端虽然打开,但是尚未从键盘输入任何信息,也就没有消息发送到服务器,对于read来说,就是无数据可读,所以会阻塞等待,直到有消息可读;现在在客户端窗口输入信息,回车后,消息被发送到服务器;服务器收到客户端发送的消息后,即read有数据可读,被唤醒,将数据读出到缓存rbuf中,然后打印收到的消息;之后再次回到accept(),由于没有新的客户端连接请求过来,所以程序就一直阻塞在accept()这里;此时如果客户端终端窗口中输入消息,会发现服务端没有任何反应,因为程序阻塞在accept()处,没有执行到read()部分,也就不会有消息打印输出。

所以上面例程中,将read()接收处理代码放到另一层while循环中,这样当打开一个客户端后,服务器就一直处于等待接收客户端消息的状态,只要客户端发送消息过来,就立即接收并打印;只有当前客户端退出时,服务器端才从新进入等待客户端连接请求状态。当然了,这里只是一个简单的示例,并不是一定要写成这样。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Linux下TCP/IP网络编程示例——实现服务器/客户端通信(一) 的相关文章

  • 前端架构图解

  • Ubuntu 18.04快捷安装ROS Melodic及rosdep update time out的问题解决

    1 ROS快捷安装 以下安装指令汇总针对Ubuntu18 04的ROS Melodic版本 xff1a 强烈建议复制以下指令到新建的xxx sh文件中 xff0c 保存后给xxx sh权限 xff0c 然后执行脚本一路输入y等候安装完成 e
  • NVIDIA Jetson AGX Xavier学习笔记3——环境配置(pytorch、torchvision、cv2)

    最近研究中需要使用NVIDIA Jetson AGX Xavier人工智能开发组件 由于也是第一次接触相关硬件设备 xff0c 遇到了很多困难 在这里记录整个Jetson AGX Xavier组件的学习过程 其中很多内容网上有比较详细的教程
  • Linux网络编程——tcp实例

    题目 1 通过TCP协议实现多个client端可以并发连接到server xff0c client可获得server指定目录下的文件列表 span class hljs comment client c Created on 2016年11
  • A星寻路算法的学习总结(详解)

    目录 1 理论基础 1 1A星寻路是用来解决什么问题的 1 2A星寻路的基本原理 2 代码实现 2 1每个格子的信息 2 2A星寻路管理器 2 3测试代码 3 实例演示 1 理论基础 1 1A星寻路是用来解决什么问题的 A星寻路是用来计算玩
  • C语言单片机栈、堆、堆栈的区别(仅供参考)

    计算机C语言中各个变量的存放区域 xff1a 代码区 xff08 CODE xff09 xff1a 存放函数代码 xff1b 静态数据区 xff08 DATA xff09 xff1a 存放全局变量 静态变量 xff1b 堆区 xff08 H
  • 用c语言写链表

    链表是数据结构的一种 xff0c 是其他三个数据结构栈 xff0c 树 xff0c 图的基础 xff0c 只有将链表这一数据结构弄懂 xff0c 才能理解其他三种数据结构 举一个例子 xff0c 老师让你设计一个联系人系统 xff0c 其中
  • Fiddler抓包工具详解

    Fiddler的详细介绍 一 Fiddler与其他抓包工具的区别 1 Firebug虽然可以抓包 xff0c 但是对于分析http请求的详细信息 xff0c 不够强大 模拟http请求的功能也不够 xff0c 且firebug常常是需要 无
  • python 解析Json对象之jsonpath_rw用法

    jsonpath rw xff1a 一个可以像写xpath一样写json的Python第三方库 首先安装 xff1a pip install jsonpath rw 实例 xff1a from jsonpath rw import json
  • selenium之xpath使用

    XPath即XML路径语言 xff0c 支持从xml或html中查找元素节点 xff0c 使用XPath完全可以替代其他定位放式 xff0c 如 xff1a find element by xpath 39 64 id 61 34 34 3
  • Python-面向对象之多态

    当子类和父类都存在相同的run 方法时 xff0c 我们说 xff0c 子类的run 覆盖了父类的run xff0c 在代码运行的时候 xff0c 总是会调用子类的run 这样 xff0c 我们就获得了继承的另一个好处 xff1a 多态 c
  • 使用Ubuntu帐户创建SFTP

    提供sftp服务的有vsftpd和internal sftp xff0c 这里用的是系统自带的internal sftp xff0c 操作步骤如下 xff1a 1 创建用户 testenv xff0c 并禁止ssh登录 xff0c 不创建家
  • flask数据分页paginate的使用(flask学习)

    Flask的数据分页示例 1 xff0c 首先写数据获取的视图函数 xff0c 就像这样 xff1a 64 app route 39 39 64 login required def index page 61 request args g
  • Python __dict__属性详解

    我们都知道Python一切皆对象 xff0c 那么Python究竟是怎么管理对象的呢 xff1f 1 无处不在的 dict 首先看一下类的 dict 属性和类对象的 dict 属性 coding utf 8 class A object 3
  • Flask-SQLAlchemy 中的 relationship & backref

    今天重看 Flask 时 xff0c 发现对backref仍然没有理解透彻 查阅文档后发现 xff0c 以前试图孤立地理解backref是问题之源 xff0c backref是与relationship配合使用的 一对多关系 db rela
  • Django HttpResponse与JsonResponse

    我们编写一些接口函数的时候 xff0c 经常需要给调用者返回json格式的数据 xff0c 那么如何返回可直接解析的json格式的数据呢 xff1f 首先先来第一种方式 xff1a from django shortcuts import
  • Ubuntu安装mysql

    首先执行下面三条命令 xff1a sudo apt get install mysql server sudo apt install mysql client sudo apt install libmysqlclient dev 安装成
  • 10种动态进度条用css3实现

    用css做的10种动态进度条 xff0c 喜欢可以直接去用话不多说先看效果图 xff1a 实现上图的 xff0c 最主要的就是应用了css动画属性 64 keyframes和animation属性结合应用 下面看看语法 xff1a 64 k
  • Yolo训练数据标注工具-Yolo_mark 使用教程

    一 安装与测试 环境 xff1a Ubuntu16 04 43 Opnecv 43 Cmake 项目地址 xff1a https github com AlexeyAB Yolo mark 下载 打开终端 xff0c 键入 xff1a gi
  • x86、ARM分属大小端

    小端模式 xff1a 一个数据的高位在大的地址端 xff0c 低位在小的地址端 xff0c x86也就是pc机就是小端的 xff1a include 34 stdio h 34 include 34 stdlib h 34 int main

随机推荐

  • 二叉树(C语言实现)——链式存储结构

    include lt stdio h gt include lt stdlib h gt include lt stdbool h gt define QueueSize 200 typedef char DataType typedef
  • 栈,堆,常量区都放什么

    1 寄存器 xff1a 最快的存储区 由编译器根据需求进行分配 我们在程序中无法控制 xff1b 1 栈 xff1a 存放基本类型的变量数据和对象的引用 xff0c 但对象本身不存放在栈中 xff0c 而是存放在堆 xff08 new 出来
  • Windows10安装Docker并创建本地Ubuntu环境

    安装Docker参考文章 xff1a https www cnblogs com Can daydayup p 15468591 html label0 安装本地Ubuntu环境 xff1a windows10下安装docker xff0c
  • 机器人操作系统ROS是什么?

    目录 1 什么是ROS 2 ROS的许可协议 3 ROS的主要发行版本 4 ROS的主要功能 5 ROS的应用 6 ROS开发的常用工具 7 ROS的优点 8 ROS的缺点 1 什么是ROS ROS是机器人操作系统 xff08 Robot
  • 【教程】如何移植FPGA关于HDMI例程

    教程 如何移植FPGA关于HDMI例程 时钟IP核约束条件 在完成EDA作业后 xff0c 抽空分享一下如何移植FPGA的例程 我EDA作业用的板子型号是Zybo Z7 xff0c 然后移植的是原子哥的HDMI实现方块移动例程 故本教程是基
  • 【MATLAB UAV Toolbox】使用指南(三)

    可视化自定义飞行日志 通过配置flightLogSignalMapping可从自定义的飞行日志中可视化数据 加载自定义的飞行日志 在本例中 xff0c 假设飞行数据已经被解析到MATLAB 中 xff0c 并存储为M文件 本示例重点介绍如何
  • matplotlib学习笔记

    matplotlib第一章 matplotlib通常有两种绘图接口 xff1a 显示创建figure和axes 依赖pyplot自动创建figure和axes 并绘图 matplotlib环境 本文是在jupyter notebook下运行
  • OPNET 修改节点图标大小

    老是记不住在哪修改图标 xff0c 有一天看急眼了 xff0c 经过半小时的斗争 xff0c 终于找到了 xff0c 这次一定要把它记下来 View gt Layout gt Scale Node icons Interactively
  • 自定义msg使用C++

    在之前创建talker的src文件夹中创建person cpp并编写如下 include 34 ros ros h 34 include 34 learning communication Person h 34 include lt ss
  • GPIO的八种模式分析

    GPIO是general purpose input output 即通用输入输出端口 xff0c 作用是负责外部器件的信息和控制外部器件工作 GPIO有如下几个特点 xff1a 1 不同型号的IO口数量不同 xff1b 2 xff0c 反
  • 关于STM32_IWDG独立看门狗的一些笔记

    独立看门狗 IWDG xff0c Independent watchdog xff0c 本质是一个可以定时产生系统复位信号 并且可以通过 喂狗 复位的计时器 它由独立的RC振荡器 低速时钟 LSI 驱动 xff0c 即使主时钟发生故障它也仍
  • 关于MPU的笔记

    MPU xff08 memory protection unit xff09 内存保护单元 这些系统必须提供一种机制来保证正在运行的任务不破坏其他任务的操作 即要防止系统资源和其他一些任务不受非法访问 嵌入式系统有专门的硬件来检测和限制系统
  • 关于OLED屏的笔记

    OLED即有机发光管 Organic Light Emitting Diode OLED OLED显示技术具有自发光 广视角 几乎无穷高的对比度 较低功耗 极高反应速度 可用于绕曲性面板 使用温度范围广 构造及制程简单等有点 xff0c 被
  • Ubuntu 上 Let‘s Encrypt 生成泛域名证书

    安装生成工具certbot xff1a apt install certbot 查看安装在哪 xff1a which certbot 使用certbot xff08 位置在 usr bin certbot xff09 生成证书 xff1a
  • DMA的补充笔记

    DMA有两个总线 xff1a 1 DMA存储器总线 xff1a DMA通过该总线来执行存储器数据的传入和传出 2 DMA外设总线 xff1a DMA通过该总线访问AHB外设 xff08 AHB主要是针对高效率 高频宽以及快速系统模块所设计的
  • 关于ADC的笔记1

    ADC xff0c 全称Anlog to Digital Converter xff0c 模拟 数字转换器 是指将连续变量的模拟信号转换为离散的数字信号的器件 xff0c 我们能通过ADC将外界的电压值读入我们的单片机中 常见的ADC有两种
  • STM32-ADC单通道采集实验

    实验要求 xff1a 通过ADC1通道 xff08 PA1 xff09 采集电位器的电压 xff0c 并显示ADC转换的数字量及换算后的电压值 首先要确定最小刻度 Vref 61 3 3V xff0c 所以输入电压有效范围在0V lt 61
  • jetson xavier nx安装ROS Melodic

    1 前期准备 打开系统设置 软件和更新 xff0c 确保图示的选项已选中 点击close xff0c 选择reload 在不同的教程里搜到的这一步都不同 xff0c 似乎没什么影响 xff0c 就很迷 2 设置你的源文件列表 设置计算机以接
  • sylixos标准工程移植到Lite版本

    1 概述 针对低端处理器 xff08 如ARM M系列处理器 xff09 的开发工作 xff0c 翼辉信息推出了SylixOS Lite工程版本 SylixOS Lite版本工程属于SylixOS轻量级工程 xff0c 与标准的SylixO
  • Linux下TCP/IP网络编程示例——实现服务器/客户端通信(一)

    一 说明 最近梳理网络编程的一些知识点时 xff0c 整理了一些笔记 xff0c 写了一些demo例程 xff0c 主要包含下面几部分 xff0c 后面会陆续完成 1 Linux下TCP IP网络编程示例 实现服务器 客户端通信 xff08