解决方案

C++网络编程(一)本地socket通信

seo靠我 2023-09-22 21:58:41

C++网络编程(一) socket通信

前言

本次内容简单描述C++网络通信中,采用socket连接客户端与服务器端的方法,以及过程中所涉及的函数概要与部分函数使用细节。记录本人C++网络学习的过程。

网络SEO靠我通信的Socket

socket,即“插座”,在网络中译作中文“套接字”,应用于计算机网络建立起数据连接。基于TCP/IP协议进行通信,将本地的ip地址与端口号相结合。两个程序可以通过各自的socketSEO靠我建立一个通道用于数据传输。

socket提供多种机制以供选择,但常用的一般为两种,即SOCK_STREAM(流)和SOCK_DGRAM(数据报)。

流socket基于TCP协议,其建立的通道是一个具有有序SEO靠我、可靠、全双工的字节流。

数据报socket基于UDP协议,其建立的通道传送数据是不可靠的,尽最大努力交付的,但传输速度较快。

目前在网络中使用的数据传送基本是基于TCP协议。

网络通信基本过程

首先建立起服SEO靠我务端,在服务端中采用socket()构建方法,建立服务端响应socket,其主要作用是用于接收由客户端socket发起的建立请求。随后bind()将创建的响应socket与本地的ip地址及设置的端口绑SEO靠我定。listen()为响应socket设置被动监听状态,监听客户端发来的建立请求。accept()代表一切就绪,此时服务器程序运行进入阻塞状态,等待客户端程序发来请求。

客户端中同样需要先建立起客户端sSEO靠我ocket,该socket所采用的协议应当与服务器端socket相同。客户端socket不需要绑定本地ip与端口,但应当指明所连接的服务器端的ip地址与端口号,通过connet()主动发起连接请求,与SEO靠我服务器端socket相连。

服务器端accept()程序收到客户端socket发来的请求后,会立即创建一个新的服务socket,与客户端的socket相对应,用于数据的收发与传送。即,服务器端会有两个sSEO靠我ocket,而客户端仅有一个socket

当双方数据交互结束后,各自调用close(),释放掉本地的socket。

代码示例

//server.cpp #include<stdio.h> SEO靠我 #include<string.h> #include<sys/socket.h> #include<netinet/in.h> #inSEO靠我clude<arpa/inet.h> #include<unistd.h> #include<cerrno>#define MY_PORT 1234//端口号 SEO靠我 #define BUF_SIZE 1024//最大缓存 #define QUEUE 20//最大连接数int main(){int server_sockfd = socketSEO靠我(AF_INET,SOCK_STREAM,0);//建立响应socketstruct sockaddr_in server_sockaddr;//保存本地地址信息server_sockaddr.sinSEO靠我_family = AF_INET;//采用ipv4server_sockaddr.sin_port = htons(MY_PORT);//指定端口server_sockaddr.sin_addr.sSEO靠我_addr = htonl(INADDR_ANY);//获取主机接收的所有响应if(bind(server_sockfd,(struct sockaddr *)&server_sockaddr,sizSEO靠我eof(server_sockaddr))==-1){//绑定本地ip与端口perror("Bind Failure\n");printf("Error: %s\n",strerror(errno))SEO靠我;//输出错误信息return -1;}printf("Listen Port : %d\n",MY_PORT);if(listen(server_sockfd,QUEUE) == -1){//设置监SEO靠我听状态perror("Listen Error");return -1;}char buffer[BUF_SIZE];//一次传输的数据缓存struct sockaddr_in client_addrSEO靠我;//保存客户端地址信息socklen_t length = sizeof(client_addr);//需要的内存大小printf("Waiting for connection!\n");int SEO靠我connect_fd = accept(server_sockfd,(struct sockaddr *)&client_addr,&length);//等待连接,返回服务器端建立连接的socketiSEO靠我f(connect_fd == -1){//连接失败perror("Connect Error");return -1;}printf("Connection Successful\n");whileSEO靠我(1){//数据收发与传输memset(buffer,0,sizeof(buffer));int len = recv(connect_fd,buffer,sizeof(buffer),0);//接收SEO靠我数据// input "exit" or runtime errorif(strcmp(buffer,"exit\n")==0 ||len <= 0) break;printf("client senSEO靠我d message: %s",buffer);strcpy(buffer,"successful");send(connect_fd,buffer,strlen(buffer),0);//发送数据prSEO靠我intf("send message: %s\n",buffer);}close(connect_fd);//关闭数据socketclose(server_sockfd);//关闭响应socketreSEO靠我turn 0; } //client.cpp #include<stdio.h> #include<string.h> SEO靠我 #include<sys/socket.h> #include<netinet/in.h> #include<arpa/inet.h> #includSEO靠我e<unistd.h> #include<cerrno>#define MY_PORT 1234 #define BUF_SIZE 1024 #defiSEO靠我ne SERVER_IP "127.0.0.1"//服务端ip,这里是本机int main(){int client_sockfd = socket(AF_INET,SOCK_STREAM,0);//SEO靠我建立客户端socketstruct sockaddr_in servaddr;//保存服务器端地址信息memset(&servaddr,0,sizeof(servaddr));servaddr.sinSEO靠我_family = AF_INET;//ipv4协议servaddr.sin_port = htons(MY_PORT);//端口servaddr.sin_addr.s_addr = inet_addSEO靠我r(SERVER_IP);//ip地址printf("connect to %s:%d\n",SERVER_IP,MY_PORT);int connect_fd = connect(client_soSEO靠我ckfd,(struct sockaddr *)&servaddr,sizeof(servaddr));//建立连接if(connect_fd<0){perror("Connect Error");_SEO靠我exit(1);}printf("Connect Successful\n");char sendbuf[BUF_SIZE];char recvbuf[BUF_SIZE];while(fgets(seSEO靠我ndbuf,sizeof(sendbuf),stdin) != NULL){//数据传送memset(recvbuf,0,sizeof(recvbuf));printf("send message:%SEO靠我s",sendbuf);send(client_sockfd,sendbuf,strlen(sendbuf),0);if(strcmp(sendbuf,"exit\n")==0) break;int SEO靠我len = recv(client_sockfd,recvbuf,sizeof(recvbuf),0);if(len<=0){printf("receive failure");break;}prinSEO靠我tf("recv message:%s\n",recvbuf);memset(sendbuf,0,sizeof(sendbuf));}close(client_sockfd);//关闭客户端sockeSEO靠我treturn 0; }

函数分析

主机字节序和网络字节序

主机字节序即在计算机内部存储数据的格式,通常为小端序,即低地址存放低字节的内容,比如说一个ipv4的地址,当其作为数字存储时,一共SEO靠我需要四个字节来保存值,对于"127.0.0.1"来说,主机字节序中,最低位的字节保存了127,最高位则是1。

网络字节序是TCP/IP中规定好的一种数据格式,采用大端序,即高地址存放低字节的内容,同样对SEO靠我于"127.0.0.1"来说,网络字节序中,最低位的字节保存了1,最高位则是127。

字节地址0123主机字节序01111111000000000000000000000001网络字节序00000001SEO靠我000000000000000001111111 #include <netinet/in.h> unsigned long int htonl(unsigned loSEO靠我ng int hostlong);//主机转网络 unsigned short int htons(unsigned short int hostshort); unsSEO靠我igned long int ntohl(unsigned long int netlong);//网络转主机 unsigned short int ntohs(unsigned shSEO靠我ort int netshort);

htonl表示host to network long,依次类推

INADDR_ANY其值为"0.0.0.0",其意义便是不监听特定的ip,而接收所有通过指定端口发送SEO靠我至本机的信号。

Socket地址

通用地址 #include <bits/socket.h>//<sys/socket.h>头文件包含了此头 struct sockaddrSEO靠我 {sa_family_t sa_family; //地址族类型char sa_data[14]; //地址值 } 专用地址

TCP/IP协议族有socSEO靠我kaddr_in和sockaddr_in6两个专用socket地址结构体,它们分别用于IPv4和IPv6,此处仅列述IPv4。

#include <netinet/in.h> strucSEO靠我t sockaddr_in {sa_family_t sin_family;/*地址族:AF_INET*/u_int16_t sin_port;/*端口号,要用网络字节序表示*/strSEO靠我uct in_addr sin_addr;/*IPv4地址结构体,见下面*/unsigned char sin_zero[8];/*为了保持与struct sockaddr长度相同*/ SEO靠我 };struct in_addr {u_int32_t s_addr;/*IPv4地址,要用网络字节序表示*/ };

在实际使用时,sockaddr_in需要转化为通用SEO靠我socket地址类型sockaddr(强制转换即可),因为所有socket编程接口使用的地址参数的类型都是sockaddr。

IP地址转换

#include <arpa/inet.h> iSEO靠我n_addr_t inet_addr(const char* strptr); int inet_aton(const char* cp,struct in_addr* inp); SEO靠我 char* inet_ntoa(struct in_addr in);

inet_addr函数将用点分十进制字符串表示的IPv4地址转化为用网络字节序整数表示的IPv4地址。它失败时返回ISEO靠我NADDR_NONE。

inet_aton函数完成和inet_addr同样的功能,但是将转化结果存储于参数inp指向的地址结构中。它成功时返回1,失败则返回0。

inet_ntoa函数将用网络字节序整数表SEO靠我示的IPv4地址转化为用点分十进制字符串表示的IPv4地址。但需要注意的是,该函数内部用一个静态变量存储转化结果,函数的返回值指向该静态内存,因此inet_ntoa是不可重入的。

注:以上函数仅用于IPSEO靠我v4的地址转换。

创建socket

#include <sys/types.h> #include <sys/socket.h> int socket(int domainSEO靠我,int type,int protocol); /* domain参数:告诉系统使用哪个底层协议族 type参数:指定服务类型 proSEO靠我tocol参数:在前两个参数构成的协议集合下,再选择一个具体的协议 */

参数通常取值:

domain:PF_INET(Protocol Family of Internet,用于IPv4SEO靠我)、PF_INET6(用于IPv6)

type:SOCK_STREAM(流,TCP)、SOCK_DGRAM(数据报,UDP)

protocol:0(默认协议)

当socket系统调用成功时返回一个sockeSEO靠我t文件描述符,失败则返回-1并设置errno。

绑定IP地址和端口

#include <sys/types.h> #include <sys/socket.h> int bSEO靠我ind(int sockfd, const struct sockaddr* my_addr, socklen_t addrlen);

bind将my_addr所指的socket地址分配给未命名的socSEO靠我kfd文件描述符,addrlen参数指出该socket地址的长度。

bind成功时返回0,失败则返回-1并设置errno。其中两种常见的errno是EACCES和EADDRINUSE,它们的含义分别是:SEO靠我

EACCES,被绑定的地址是受保护的地址,仅超级用户能够访问。比如普通用户将socket绑定到知名服务端口(端口号为0~1023)上时,bind将返回EACCES错误。EADDRINUSE,被绑定的地SEO靠我址正在使用中。比如将socket绑定到一个处于TIME_WAIT状态的socket地址。

监听socket

#include <sys/socket.h> int listen(int sSEO靠我ockfd, int backlog); /* sockfd: 指定被监听的socket backlog: 设置内核监听队列的最大长度 SEO靠我*/

监听队列:即处于向服务端socket发出连接请求,但TCP建立三次握手中尚未进行第二次握手的序列。

监听队列的长度如果超过backlog,服务器将不受理新的客户连接,客户端也将收到ECONNREFUSEO靠我SED错误信息。

listen成功时返回0,失败则返回-1并设置errno。

注:listen()函数执行成功后,客户端发来连接请求,在经过listen后,TCP的三次握手已经完成,建立了连接。

接收连接

#SEO靠我include <sys/types.h> #include <sys/socket.h> int accept(int sockfd, struct sockaddrSEO靠我* addr, socklen_t* addrlen); /* sockfd:是执行过listen系统调用的监听socket addr:用来获取被接受连SEO靠我接的远端socket地址 addrlen:指出远程socket的长度 返回值:一个新的连接socket */

函数调用成功返回的socket唯一地标识了被SEO靠我接受的这个连接,服务器可通过读写该socket来与被接受连接对应的客户端通信。

调用失败则返回-1并设置errno。

发起连接

#include <sys/types.h> #includeSEO靠我 <sys/socket.h> int connect(int sockfd, const struct sockaddr* serv_addr, socklen_t addrlen)SEO靠我;

sockfd参数由socket系统调用返回一个socket。serv_addr参数是服务器监听的socket地址,addrlen参数则指定这个地址的长度。

connect成功时返回0。一旦成功建立连接SEO靠我,sockfd就唯一地标识了这个连接,客户端就可以通过读写sockfd来与服务器通信。connect失败则返回-1并设置errno。其中两种常见的errno是ECONNREFUSED和ETIMEDOUSEO靠我T,它们的含义如下:

ECONNREFUSED,目标端口不存在,连接被拒绝。ETIMEDOUT,连接超时。

关闭连接

#include <unistd.h> int close(int fdSEO靠我);//fd是待关闭的socket

close系统调用并非总是立即关闭一个连接,而是将fd的引用计数减1。只有当fd的引用计数为0时,才真正关闭连接。类似于Linux系统中的硬链接数。比方说在多进程程序SEO靠我中,父进程和子进程都对一个socket操作,只有在所有进程中都调用close()关闭该socket,该socket才会被关闭。

有一个函数可以立即终止连接:

#include <sys/socket.h>SEO靠我 int shutdown(int sockfd,int howto);

sockfd参数是待关闭的socket。howto参数决定了shutdown的行为,可取值如下:

SHUT_RD SEO靠我关闭读SHUT_WR 关闭写SHUT_RDWR 关闭读写

数据读写

#include <sys/types.h> #include <sys/socket.h> ssizeSEO靠我_t recv(int sockfd, void* buf, size_t len, int flags); ssize_t send(int sockfd, const void* SEO靠我buf, size_t len, int flags);

此处函数仅针对TCP流数据读写,对UDP数据报的读写暂且不表。

recv读取sockfd上的数据,buf和len参数分别指定读缓冲区的位置和大小,SEO靠我flags通常设置为0即可。recv成功时返回实际读取到的数据的长度,它可能小于我们期望的长度len。因此我们可能要多次调用recv,才能读取到完整的数据。recv可能返回0,这意味着通信对方已经关闭SEO靠我连接了。recv出错时返回-1并设置errno。

send往sockfd上写入数据,buf和len参数分别指定写缓冲区的位置和大小。send成功时返回实际写入的数据的长度,失败则返回-1并设置errnoSEO靠我

网络信息API

#include <netdb.h> struct hostent*gethostbyname(const char* name);//name:指定目标主机的主机名 SEO靠我 struct hostent*gethostbyaddr(const void* addr, size_t len, int type); /* addSEO靠我r:指定目标主机的IP地址 len:指定addr所指IP地址的长度 type:指定addr所指IP地址的类型(AF_INTE、AF_INET6) */sSEO靠我truct hostent {char* h_name;/*主机名*/char** h_aliases;/*主机别名列表,可能有多个*/int h_addrtype;/*地址类型(地址SEO靠我族)*/int h_length;/*地址长度*/char** h_addr_list;/*按网络字节序列出的主机IP地址列表(数字形式,不是点分十进制)*/ };

gethostbynSEO靠我ame函数根据主机名称获取主机的完整信息,gethostbyaddr函数根据IP地址获取主机的完整信息。gethostbyname函数通常先在本地的/etc/hosts配置文件中查找主机,如果没有找到SEO靠我,再去访问DNS服务器。

//示例 struct hostent *res = gethostbyname("www.baidu.com"); if (res) SEO靠我 {for (int i = 0; res->h_addr_list[i]; i++)printf("IP addr %d: %s\n", i + 1, inet_ntoa(*(struct iSEO靠我n_addr *)res->h_addr_list[i])); }
“SEO靠我”的新闻页面文章、图片、音频、视频等稿件均为自媒体人、第三方机构发布或转载。如稿件涉及版权等问题,请与 我们联系删除或处理,客服邮箱:html5sh@163.com,稿件内容仅为传递更多信息之目的,不代表本网观点,亦不代表本网站赞同 其观点或证实其内容的真实性。

网站备案号:浙ICP备17034767号-2