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

这篇具有很好参考价值的文章主要介绍了C/C++服务器和客户端交互笔记。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

C/C++服务器开发

网络与通信Socket

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

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

Socket通信模型
C/C++服务器和客户端交互笔记,c++,服务器,笔记
telnet ipxx 进行主机间通信。

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

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

#define SERVER_PORT 666

int main(void)
{

    int sock;

    struct sockaddr_in server_addr;

    sock = socket(AF_INET, SOCK_STREAM, 0);

    // printf("wait \n");

    bzero(&server_addr, sizeof(server_addr));

    server_addr.sin_family = AF_INET;
    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("wait client connect\n");
    

    int done = 1;
    while (done)
    {
        struct sockaddr_in client;
        int client_sock, len, i;
        char client_ip[64];
        char buf[256];

        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 is : %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("finish. len:%d\n", len);
        close(client_sock);
    }
    close(sock);

    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来描述网络连接的一对一关系。

C/C++服务器和客户端交互笔记,c++,服务器,笔记
网络通讯中,套接字一定是成对出现的。一段的发送缓冲区对应另一端的接收缓冲区。使用同一个文件描述符发送缓冲区和接收缓冲区。

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

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

网络字节序

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

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

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还是其他的,由地址族确定,然后函数内部再强制类型转化为所需的地址类型。
C/C++服务器和客户端交互笔记,c++,服务器,笔记

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的参数是对方的地址。

出错处理函数

系统调用不能保证每次执行都成功,应该尽快获得程序故障信息。文章来源地址https://www.toymoban.com/news/detail-535437.html

//头文件
#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输出出错原因(控制台打印)。

到了这里,关于C/C++服务器和客户端交互笔记的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 《TCP/IP网络编程》阅读笔记--基于TCP的服务器端/客户端

    目录 1--TCP/IP协议栈 2--TCP服务器端默认函数调用顺序 3--TCP客户端的默认函数调用顺序 4--Linux实现迭代回声服务器端/客户端 5--Windows实现迭代回声服务器端/客户端 6--TCP原理 7--Windows实现计算器服务器端/客户端         TCP/IP协议栈共分 4 层,可以理解为数据收发分成了 4 个层

    2024年02月10日
    浏览(43)
  • Flask框架小程序后端分离开发学习笔记《3》客户端向服务器端发送请求

    Flask是使用python的后端,由于小程序需要后端开发,遂学习一下后端开发。 可以看到上述代码构建了一个HTTP请求,在发送之前需要将发送之前,使用http_request.encode(‘utf-8’)。 这是因为我们在这构建的请求是字符串文本,而电脑只认识二进制0和1,所以需要将其数据类型转换

    2024年01月22日
    浏览(43)
  • 《TCP/IP网络编程》阅读笔记--基于Windows实现Hello Word服务器端和客户端

    目录 1--Hello Word服务器端 2--客户端 3--编译运行 3-1--编译服务器端 3-2--编译客户端 3-3--运行 运行结果:

    2024年02月10日
    浏览(44)
  • UnityMirror学习笔记(3):服务器与客户端间的函数调用,互发控制指令:Command,ClientRpc,TargetRpc

    Mirror是一个简单高效的开源的Unity多人游戏网络框架。 官方文档链接: https://mirror-networking.gitbook.io/docs API Mirror提供了一个特性 Command 来进行从客户端到服务器的远程控制指令, 它的核心逻辑就是,当 客户端 的脚本调用此特性修饰的函数时, 服务端 的 对应的对象 执行此函数

    2024年02月07日
    浏览(27)
  • 服务器异步客户端

    internal class MessageManagerT:SingletonMessageManagerT {     Dictionaryint, ActionT MsgDic = new Dictionaryint, ActionT();     public void OnAddListen(int id,ActionT action)     {         if(MsgDic.ContainsKey(id))         {             MsgDic[id] += action;         }         else         {             MsgDic.Add(id, ac

    2024年04月09日
    浏览(42)
  • UDP通讯(服务器/客户端)

    前言:UDP通讯实现比较简单,单某些情况下也会使用,建议先看一下说明,然后运行代码感受一下。         传输层主要应用的协议模型有两种,一种是TCP协议,另外一种则是UDP协议。TCP协议在网络通信中占主导地位,绝大多数的网络通信借助TCP协议完成数据传输。但U

    2024年02月03日
    浏览(49)
  • UDP客户端和服务器

    UDP客户端,也就是首先主动发送数据的一方,也就是发起服务请求的一方。 UDP服务器,也就是首先等待接收数据,并对接收的数据进行处理,返回计算结果的一方,也就是提供服务的一方。 在下面实验中使用到的函数 测试代码  UDP接收端(服务器) 执行代码:    实验解析 

    2024年02月13日
    浏览(74)
  • ZeroTier客户端连接服务器

    ZeroTier客户端连接服务器 下载客户端 https://www.zerotier.com/download/ 加入新的网络(例如d5e04297a16fa690,由管理员提供) 管理员授权并告知服务器IP 测试连接:ping 服务器IP 使用putty, pycharm, vscode等工具连接即可 官方文档 https://docs.zerotier.com/getting-started/getting-started 注: 若路由器无法

    2024年02月09日
    浏览(34)
  • Java 构建websocket客户端,构建wss客户端,使用wss连接,并发送数据到服务器端,接收服务器端消息

    Java 构建websocket客户端,构建wss客户端,使用wss连接,并发送数据到服务器端,接收服务器端消息 回调函数处理

    2024年02月13日
    浏览(45)
  • netty构建udp服务器以及发送报文到客户端客户端详细案例

    目录 一、基于netty创建udp服务端以及对应通道设置关键 二、发送数据 三、netty中的ChannelOption常用参数说明 1、ChannelOption.SO_BACKLOG 2、ChannelOption.SO_REUSEADDR 3、ChannelOption.SO_KEEPALIVE 4、ChannelOption.SO_SNDBUF和ChannelOption.SO_RCVBUF 5、ChannelOption.SO_LINGER 6、ChannelOption.TCP_NODELAY 以上代码中

    2024年04月09日
    浏览(51)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包