C/C++服务器和客户端交互笔记

news2024/12/23 14:11:11

C/C++服务器开发

网络与通信Socket

Socket通信三要素:通信的目的地址、使用的端口号(http 80 / smtp 25)、使用的传输协议(TCP、UDP)。

nslookup xx 可以查询xx网址的IP地址。

Socket通信模型

telnet ipxx 进行主机间通信。

一个简单的服务器和客户端通信程序,服务器端代码:

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<string.h>
#include<ctype.h>
#include<arpa/inet.h>

#define SERVER_PORT 666
int main(void){
	// 设置信箱
	int sock;
	// 已经声明了sockaddr_in结构体,直接调用就行
	struct sockaddr_in server_addr;
	// 
	sock=socket(AF_INET,SOCK_STREAM,0);

	bzero(&server_addr,sizeof(server_addr));

	// 选择协议族IPV4
	server_addr.sin_family = AF_INET;
	// 监听本地的所有IP地址 INADDR_ANY其实是0.0.0.0(整型数)
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	// 绑定端口号
	server_addr.sin_port = htons(SERVER_PORT);
	//将标签贴到信箱上
	bind(sock, (struct sockaddr *)&server_addr, sizeof(server_addr));
	//把信箱挂置传达室,就可以接收到信了
	listen(sock, 128);

	printf("等待客户端的连接");
	int done = 1;
	while(done){
		struct sockaddr_in client;
		int client_sock,len;

		char client_ip[64];

		socklen_t client_addr_len;
		client_addr_len = sizeof(client);
		client_sock = accept(sock, (struct sockaddr *)&client, &client_addr_len);

		printf("client ip: %s\t port : %d\n", inet_ntop(AF_INET, &client.sin_addr.s_addr, client_ip, sizeof(client_ip)), ntohs(client.sin_port));

		/*读取客户端发送的数据*/
		len = read(client_sock, buf, sizeof(buf) - 1);
		buf[len] = '\0';
		printf("receive[%d]: %s\n", len, buf);


		len = write(client_sock, buf, len);
		printf("finished. len: %d\n", len);

	}
	return 0;
}

客户端代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define SERVER_PORT 666
#define SERVER_IP  "127.0.0.1"

int main(int argc, char *argv[]){

    int sockfd;
    char *message;
    struct sockaddr_in servaddr;
    int n;
    char buf[64];

    if(argc != 2){
        fputs("Usage: ./echo_client message \n", stderr);
        exit(1);
    }

    message = argv[1];

    printf("message: %s\n", message);

    sockfd = socket(AF_INET, SOCK_STREAM, 0);
	//重置结构体的内存空间
    memset(&servaddr, '\0', sizeof(struct sockaddr_in));

    servaddr.sin_family = AF_INET;
    inet_pton(AF_INET, SERVER_IP, &servaddr.sin_addr);
    servaddr.sin_port = htons(SERVER_PORT);

    connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

    write(sockfd, message, strlen(message));

    n = read(sockfd, buf, sizeof(buf)-1);

    if(n>0){
        buf[n]='\0';
        printf("receive: %s\n", buf);
    }else {
        perror("error!!!");
    }

    printf("finished.\n");
    close(sockfd);

    return 0;
}

Socket概念

socket(套接字)的中文意思为插座,socket一般用整型表示,Linux中,表示进程x间网络通信的特殊文件类型。本质上为内核借助缓冲区形成的为文件。可以使用文件描述符引用套接字。Linux系统将其封装成文件的目的是为了统一接口,使读写套接字和读写文件操作一致。区别在于文件主要因用于本地持久化数据的读写,而套接字多应用于网络进程间数据的传递。

在TCP/IP协议中,IP地址-TCP或UDP端口号 唯一标识网络通讯中的一个进程。IP地址+端口号 就对应一个socket。与建立连接的两个进程各有一个socket来标识,那么这两个socket组成的socket pair就唯一标识一个连接。因此可以用Socket来描述网络连接的一对一关系。


网络通讯中,套接字一定是成对出现的。一段的发送缓冲区对应另一端的接收缓冲区。使用同一个文件描述符发送缓冲区和接收缓冲区。


服务器和客户端之间的通讯是全双工的,可以互相读写,采用同步和异步的方式进行交互。

四次挥手结束客户端和服务器端的通讯。

网络字节序

大端字节序-低地址高字节,高地址低字节
小端字节序-低地址低字节,高地址高字节

内存中的多字节数据相对于内存地址、磁盘文件中的多字节数据相对于文件中的偏移地址,网络数据流都有大端和小端之分。发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出,接收主机把从网络上接到的字节一次保存在接受缓冲区中,也是按照内存地址从低到高的顺序保存。因此,网络数据流的地址应该这样规定:先发出的数据是低地址,后发出的数据是高地址。

TCP/IP协议规定,网络数据流应采用大端字节序,既低地址高字节

32位IP地址也要考虑网络字节序和主机字节序的问题。C/C++中采用一下库函数进行网络字节序和主机字节序的转换。

//头文件,库函数
#include<arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);

h表示hostn表示networkl表示32位长整型,s表示16位短整型。

如果主机是小端字节序,这些函数将参数做相应的大小端转化然后返回,如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回

SocketAddr详解

很多网络编程函数诞生早于IPv4协议,那时候都使用的是sockaddr结构体,为了向前兼容,现在sockaddr退化成了(void *)的作用,传递一个地址给函数,至于这个函数是sockaddr_in还是其他的,由地址族确定,然后函数内部再强制类型转化为所需的地址类型。

SocketAddress结构图
struct sockaddr {
	sa_family_t sa_family;/* address family, AF_xxx AF_INET(IPV4) AF_INET(IPV6)*/
	char sa_data[14];     /* 14 bytes of protocol address */
};

 struct sockaddr_in {
     sa_family_t    sin_family; /* address family: AF_INET */
     in_port_t      sin_port;   /* port in network byte order */
     struct in_addr sin_addr;   /* internet address */
 };

 /* Internet address. */
struct in_addr {
    uint32_t       s_addr;     /* address in network byte order */
};

IPv4的地址格式定义在netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位端口号和32位IP地址,但是sock API的实现早于ANSI C标准化,那时还没有void *类型,因此这些像bind accept函数的参数都用struct sockaddr *类型表示,在传递参数之前要强制类型转换一下,例如:

struct sockaddr_in servaddr;
bind(listen_fd, (struct sockaddr *)&servaddr, sizeof(servaddr));/* initialize servaddr */

IP地址转化

#include <arpa/inet.h>
//将字符串的IP转化为网络的整型IP
int inet_pton(int af, const char *src, void *dst);
//将网络字节序的IP转化为字符串的IP
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);

af 取值可选为 AF_INETAF_INET6 ,即和 ipv4 和ipv6对应支持IPv4和IPv6
假设主机地址位2.3.4.5,其中2表示低位,5表示高位,则大端字节序的结果为5040302(使用inet_pton(AF_INET,"2.3.4.5",&s_add)进行转化),小端字节序为2030405(使用ntohl(s_addr)进行转化)。

ipconfig /all查看主机的网络地址。

Socket编程

socket函数

//头文件
#include<sys/types.h>
#include<sys/socket.h>

int socket(int doamin,int type,int protocol);

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

socket()打开一个网络通讯端口,如果成功的话,就像open()一样返回一个文件描述符,应用程序可以向读写文件一样用read/write在网络上收发数据,如果socket()调用出错则返回-1。

bind函数

服务器将程序所监听的网络地址和端口号通常是固定不变的,客户端程序得知服务器程序的地址和端口号就可以向服务器发起连接,因此服务器需要调用bind进行绑定。

//头文件
#include<sys/type.h>
#include<sys/socket.h>

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

sockfd:
	socket文件描述符
addr:
	购找出IP地址加端口号
addrlen:
	sizeof(addr)长度
return:
	成功返回0。失败返回-1,设置errno。

bind()的作用是将参数sockfdaddr绑定在一起,使sockfd这个用于网络通讯的文件描述符监听addr所描述的地址和端口号。struct sockaddr *是一个通用指针类型,addr参数实际上可以接受多种协议的sockaddr结构体,而它们的长度各不相同,所以需要第三个参数addrlen指定结构体的长度。

struct sockaddr_in servaddr;
//结构体清空很重要
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(6666);

Listen函数

典型的服务器程序可以同时服务与多个客户端,当有客户端发起连接时,服务器调用的accept()函数返回并接受这个连接,如果有大量的可u段发起连接而服务器来不及处理,桑威accept的客户端就处于这个连接等待状态,listen()僧名sockfd处于监听状态,如果接受到更多的连接请求就忽略,listen()成功返回0,失败返回-1.

//头文件
#include<sys/types.h>
#include<sys/socket.h>

int listen(int sockfd,int backlog);

sockfd:
	socket文件描述符
backlog:
	在Linux系统中,它是指排队等待建立3次握手队列长度

查看系统默认backlog

cat /proc/sys/net/ipv4/tcp_max_syn_backlog

accept函数

//头文件
#include<sys/types.h>
#include<sys/socket.h>

int accept(int sockfd,struct aockaddr *addr,socklen_t *addrlen);

sockdf:
	socket文件描述符
addr:
	传出的参数,返回链接客户端地址信息,含IP地址和端口号
addrlen:
	传入传出参数(值-结果),传入sizeof(addr)大小,函数返回时返回真正接受到地址结构体的大小
return:
	返回一个新的socket文件描述符,用于和客户端通信,失败返回-1,并设置errno

三次握手以后,服务器调用accept()接受连接,如果服务器调用accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来。addr是一个传出参数,accept()返回时传出客户端的地址和端口号。

服务器端代码结构案例:

while (1) {
	cliaddr_len = sizeof(cliaddr);
	//如果没有客户端连接就会一直堵塞在这个代码上,不会往下进行执行
	connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
	n = read(connfd, buf, MAXLINE);
	.......
	close(connfd);
}

整个结构是一个while死循环,每次循环处理一个客户端连接,由于cliaddr_len是一个传入传出参数,每次调用accept()之前应该重新赋初值。accept()的参数listenfs是先前监听的文件描述符,而accept()的返回值是另外一个文件描述符connfd,之后与客户端之间就是通过或者connfd通讯,最后关闭connfd断开连接,而不关闭listenfd,再次回到循环开头listenfd仍然用作accept参数。accept()成功返回一个文件描述符,出错返回-1。

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:
	传入参数,指定服务器段地址信息,含IP地址和端口信息
return:
	成功返回0,失败返回-1,并设置errno

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

出错处理函数

系统调用不能保证每次执行都成功,应该尽快获得程序故障信息。

//头文件
#include<errno.h>
#include<string.h>

char *strerror(int errnum)
errnum:
	传入参数,错误编号的值,一般去errno的值
return:
	错误原因
	
#include<stdio.h>
#include<errno.h>
void perror(const char *s);
s:
	传入参数,自定义描述
return:
	无
向标准出错stdeer输出出错原因(控制台打印)。

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

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

相关文章

在ik分词器中自定义配置分词

找到ik分词器安装目录下的IKAnalyzer.cfg.xml配置文件 打开后就有让我们配置扩展字典的位置,还有停止的,这里的文件名完全自定义 当写完后然后再安装目录下创建这两个文件夹 在创建完成后重启elasticsearch即可 如果配置未生效很有可能是文件的编码格式有问题,我们将编码改为UT…

Linux-扩展篇-RPM和Yum-克隆和快照

扩展篇 学自尚硅谷武晟然老师&#xff0c;结合老师课堂内容和自己笔记所写博文。 文章目录 扩展篇软件包管理一、RPM1、RPM概述2、命令查询命令卸载命令安装命令 二、yum1、yum概述2、命令3、修改网络yum源 克隆虚拟机克隆快照 Shell编程Shell 概述 软件包管理 一、RPM 1、RP…

WIN11系统安装MySql8.0.15详细安装

一.下载mysql8.015数据库 下载地址&#xff1a; 如下图所示 此处下载的是8.0.15版本&#xff0c;免安装版&#xff0c;系统为64位系统&#xff1a; 二&#xff0c;配置mysql环境变量: D:\program_file_worker\mysql8.15\mysql-8.0.15-winx64\bin 三. 环境配置完成后&#xff…

优化设备管理,半导体CMS系统的重要性和优势解析

在半导体制造行业中&#xff0c;设备管理对于企业的生产效率和成本控制起着至关重要的作用。随着技术的不断进步和市场的竞争加剧&#xff0c;企业需要更加精细化、智能化地管理设备&#xff0c;以提高生产效率、降低维修成本&#xff0c;并确保产品质量的稳定性。 图.半导体芯…

vue 对话框内容超出组件问题

遇到这种问题该怎么解决, 样式问题 很好解决 解决方案很简单: 用flex布局的flex-wrap: wrap 数据 自适应布局 水了一篇 哈哈哈

管理类联考——数学——趣味篇——公式——图形推导

&#x1f3e0;个人主页&#xff1a;fo安方的博客✨ &#x1f482;个人简历&#xff1a;大家好&#xff0c;我是fo安方&#xff0c;考取过HCIE Cloud Computing、CCIE Security、CISP、RHCE、CCNP RS、PEST 3等证书。&#x1f433; &#x1f495;兴趣爱好&#xff1a;b站天天刷&…

SNMP 计算机网络管理 实验1(三) 练习与使用Wireshark抓取SNMP数据包抓包之任务四 分析并验证ARP协议数据单元的格式;

⬜⬜⬜ &#x1f430;&#x1f7e7;&#x1f7e8;&#x1f7e9;&#x1f7e6;&#x1f7ea;(*^▽^*)欢迎光临 &#x1f7e7;&#x1f7e8;&#x1f7e9;&#x1f7e6;&#x1f7ea;&#x1f430;⬜⬜⬜ ✏️write in front✏️ &#x1f4dd;个人主页&#xff1a;陈丹宇jmu &am…

【python】__init__.py 文件的作用

先看文件夹组成&#xff1a; 可以看到&#xff0c;几乎每个文件夹下都有__init__.py&#xff0c;一个目录如果包含了__init__.py 文件&#xff0c;那么它就变成了一个包&#xff08;package&#xff09;。__init__.py可以为空&#xff0c;也可以定义包的属性和方法&#xff0…

Java开发 - Canal进阶之和Redis的数据同步

前言 Canal在数据同步中是非常常见的&#xff0c;一般我们会用它来做MySQL和Redis之间、MySQL和ES之间的数据同步&#xff0c;否则就是手动通过代码进行同步&#xff0c;造成代码耦合度高的问题&#xff0c;这并不是我们愿意看见的&#xff0c;今天这篇博客博主将给大家演示Ca…

速下载|2023上半年网络与数据安全法规政策、国标、报告合集

随着国家数字经济建设进程加快&#xff0c;数据安全立法实现由点到面、由面到体加速构建&#xff0c;目前我国数据安全立法已基本形成以《网络安全法》《数据安全法》《个人信息保护法》《密码法》等法律为核心&#xff0c;行政法规、部门规章为依托&#xff0c;地方性法规、地…

【全文搜索选型】全文搜索 PostgreSQL 或 ElasticSearch

在本文中&#xff0c;我记录了在 PostgreSQL&#xff08;使用 Django ORM&#xff09;和 ElasticSearch 中实现全文搜索 (FTS) 时的一些发现。 作为一名 Django 开发人员&#xff0c;我开始寻找可用的选项来在大约一百万行的标准大小上执行全文搜索。有两个值得尝试的选项&…

百度文心一言App已在AppStore上架—特别是发现页的功能太强大了

百度文心一言 App 现已上架苹果 App Store&#xff0c;所有用户可免费下载安装。 特别是发现页的功能&#xff0c;真的太强大了&#xff0c;基本涵盖了你所有已知的 AI 工具功能&#xff01;比如&#xff1a; 小红书探店文案、风格头像、朋友圈神器、短视频脚本生成、AI 绘画…

【ESP32 开发】| Clion 搭建 ESP32 开发环境

目录 前言1 软件以及所需工具2 安装 ESP-IDF 4.4.42.1 开始安装2.2 选择组件&#xff0c;建议全选 3 用 ESP-IDF 4.4 CMD 添加环境变量并新建工程3.1 打开 ESP-IDF 4.4 CMD 初始化环境变量3.2 切到工作路径并新建工程 4 配置 Clion 开发环境4.1 用 Clion 打开新建的工程文件4.2…

有了企业网盘,为什么要需要知识文档管理系统

关键词&#xff1a;企业网盘、知识文档管理系统、群晖NAS 编者按&#xff1a;随着企业办公室自动化的要求越来越明显&#xff0c;企业对于文档存储的需求也逐渐加大。企业网盘的出现解决了公司文件数据储存等难题。但随着企业的文档数据逐渐增多&#xff0c;如何安全管理企业重…

蓝牙资讯|苹果AirPods Pro充电盒将换用USB-C接口,还有新功能在测试

据彭博社记者 Mark Gurman 在他的最新一期 Power On 时事通讯中报道&#xff0c;苹果正准备推出适用于 AirPods Pro 的 USB-C 充电盒&#xff0c;大概会在今年秋天与 iPhone 15 系列一起推出&#xff0c;后者也将从 Lightning 端口切换到 USB-C 端口。 此外&#xff0c;苹果也…

Java集合之Disruptor 介绍

文章目录 1 Disruptor1.1 简介1.1.1 定义1.1.2 Java中线程安全队列1.1.3 Disruptor 核心概念 1.2 操作1.2.1 坐标依赖1.2.2 创建事件1.2.3 创建事件工厂1.2.4 创建处理事件Handler--消费者1.2.5 初始化 Disruptor1.2.5.1 静态类1.2.5.2 配置类1.2.5.3 Disruptor 构造函数讲解 1…

uniapp仿浙北汇生活微信小程序

最近给公司写了一个内部微信小程序&#xff0c;功能比较简单&#xff0c;之前是用微信小程序原生写的&#xff0c;一遍看文档一边写&#xff0c;js&#xff0c;wxml&#xff0c;wxcc&#xff0c;json分在不同文件的写法很不习惯&#xff0c;于是花了两天用uniapp重写了一遍&…

TextMining day1 电力设备运维过程中的短文本挖掘框架

电力设备运维过程中的短文本挖掘框架 III. 短文本挖掘框架的具体设计A. 预处理模块的具体设计B. 数据清洗模块的具体设计C. 表示模块的具体设计D. 数据分析模块的具体设计 IV. 案例研究A. 基于文本分类的缺陷程度判断B. 基于文本检索的缺陷处理决策 V. 结论 预处理 首先&#x…

一个光模块可以带动多少户

随着科技的快速发展&#xff0c;光模块的应用场景逐渐扩大&#xff0c;数据中心、人工智能AI的创新使我们的生活日新月异。今天我们就来看看一个小小的光模块究竟蕴藏着多大的能量&#xff01; 一、影响光模块带动户数的因素 光模块是一种实现光电转换和电光转换功能的光电子…

android Surface(1, 2)

android Surface(1, 2) android的Surface相关内容从底层依次往上分别是&#xff1a; 1.frameBuffer&#xff0c;简称fb&#xff0c;对于同一个android系统&#xff0c;可以同时存在多个frameBuffer&#xff0c;本机是fb0&#xff0c;依次外接时&#xff0c;fb1, fb2, ……fbn…