0


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

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<ctype.h>#include<arpa/inet.h>#defineSERVER_PORT666intmain(void){int sock;structsockaddr_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,(structsockaddr*)&server_addr,sizeof(server_addr));listen(sock,128);printf("wait client connect\n");int done =1;while(done){structsockaddr_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,(structsockaddr*)&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);return0;}

客户端代码:

#include<stdio.h>#include<stdlib.h>#include<string.h>#include<unistd.h>#include<sys/socket.h>#include<netinet/in.h>#defineSERVER_PORT666#defineSERVER_IP"127.0.0.1"intmain(int argc,char*argv[]){int sockfd;char*message;structsockaddr_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(structsockaddr_in));

    servaddr.sin_family = AF_INET;inet_pton(AF_INET, SERVER_IP,&servaddr.sin_addr);
    servaddr.sin_port =htons(SERVER_PORT);connect(sockfd,(structsockaddr*)&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);return0;}

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_thtonl(uint32_t hostlong);uint16_thtons(uint16_t hostshort);uint32_tntohl(uint32_t netlong);uint16_tntohs(uint16_t netshort);
h

表示

host

n

表示

network

l

表示32位长整型,

s

表示16位短整型。

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

SocketAddr详解

很多网络编程函数诞生早于IPv4协议,那时候都使用的是

sockaddr

结构体,为了向前兼容,现在

sockaddr

退化成了

(void *)

的作用,传递一个地址给函数,至于这个函数是

sockaddr_in

还是其他的,由地址族确定,然后函数内部再强制类型转化为所需的地址类型。

SocketAddress结构图

structsockaddr{
    sa_family_t sa_family;/* address family, AF_xxx AF_INET(IPV4) AF_INET(IPV6)*/char sa_data[14];/* 14 bytes of protocol address */};structsockaddr_in{
     sa_family_t    sin_family;/* address family: AF_INET */
     in_port_t      sin_port;/* port in network byte order */structin_addr sin_addr;/* internet address */};/* Internet address. */structin_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 *

类型表示,在传递参数之前要强制类型转换一下,例如:

structsockaddr_in servaddr;bind(listen_fd,(structsockaddr*)&servaddr,sizeof(servaddr));/* initialize servaddr */

IP地址转化

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

取值可选为

AF_INET

AF_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>intsocket(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>intbind(int sockfd,conststructsockaddr*addr,socklen_t addren);

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

的作用是将参数

sockfd

addr

绑定在一起,使

sockfd

这个用于网络通讯的文件描述符监听

addr

所描述的地址和端口号。

struct sockaddr *

是一个通用指针类型,

addr

参数实际上可以接受多种协议的

sockaddr

结构体,而它们的长度各不相同,所以需要第三个参数

addrlen

指定结构体的长度。

structsockaddr_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>intlisten(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>intaccept(int sockfd,structaockaddr*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,(structsockaddr*)&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>intconnect(int sockfd,conststructsockaddr*addr,socklen_t addrlen);
sockdf:
    socket文件描述符
addr:
    传入参数,指定服务器端地址信息,含IP地址和端口号
addrlen:
    传入参数,指定服务器段地址信息,含IP地址和端口信息
return:
    成功返回0,失败返回-1,并设置errno

客户端需要调用

connect()

连接服务器,

connect

bind

的参数一致,区别在于

bind

的参数是自己的地址,而

connect

的参数是对方的地址。

出错处理函数

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

//头文件#include<errno.h>#include<string.h>char*strerror(int errnum)
errnum:
    传入参数,错误编号的值,一般去errno的值
return:
    错误原因
    
#include<stdio.h>#include<errno.h>voidperror(constchar*s);
s:
    传入参数,自定义描述
return:
    无
向标准出错stdeer输出出错原因(控制台打印)。
标签: c++ 服务器 笔记

本文转载自: https://blog.csdn.net/qq_45041871/article/details/131513078
版权归原作者 Trouble.. 所有, 如有侵权,请联系我们删除。

“C/C++服务器和客户端交互笔记”的评论:

还没有评论