网站内容的编辑和更新怎么做的,wordpress仿商城,wordpress地图页面如何添加图片,龙华做网站目录流程概述服务器端代码实现客户端代码实现函数和结构讲解sockaddr_in和sockaddrsocket #xff1a; 创建一个socket连接bind #xff1a;绑定地址以及端口号问题流程概述
客户端与服务器之间的网络通信基本原理如下所示#xff0c;复杂一点的架构可能会添加消息中间件。… 目录流程概述服务器端代码实现客户端代码实现函数和结构讲解sockaddr_in和sockaddrsocket 创建一个socket连接bind 绑定地址以及端口号问题 流程概述
客户端与服务器之间的网络通信基本原理如下所示复杂一点的架构可能会添加消息中间件。 对于服务端通信流程如下
1、调用socket函数创建监听socket
2、调用bind函数将socket绑定到某个IP和端口号组成的二元组上
3、调用listen函数开启监听
4、当有客户端连接请求时调用accept函数接受连接产生一个新的socket与客户端通信的socket
5、基于新产生的socket调用send或recv函数开始与客户端进行数据交流
6、通信结束后调用close函数关闭socket对于客户端通信流程如下
1、调用socket函数创建客户端socket
2、调用connect函数尝试连接服务器
3、连接成功后调用send或recv函数与服务器进行数据交流
4、通信结束后调用close函数关闭监听socket服务器端代码实现
#include iostream
#include sys/types.h
#include arpa/inet.h
#include unistd.h
#include string.husing namespace std;
int main() {// 创建一个监听socketint listenfd socket(AF_INET, SOCK_STREAM, 0);if (listenfd -1) {cout create listen socket error endl;return -1;}// 初始化服务器地址struct sockaddr_in bindaddr;bindaddr.sin_family AF_INET;bindaddr.sin_addr.s_addr htonl(INADDR_ANY);bindaddr.sin_port htons(3000);if (bind(listenfd, (struct sockaddr *) bindaddr, sizeof(bindaddr)) -1) {cout bind listen socket error endl;return -1;}// 启动监听if (listen(listenfd, SOMAXCONN) -1) {cout listen error endl;return -1;}while (true) {// 创建一个临时的客户端socketstruct sockaddr_in clientaddr;socklen_t clientaddrlen sizeof(clientaddr);// 接受客户端连接int clientfd accept(listenfd, (struct sockaddr *) clientaddr, clientaddrlen);if (clientfd ! -1) {char recvBuf[32] {0};// 从客户端接受数据int ret recv(clientfd, recvBuf, 32, 0);if (ret 0) {cout recv data from cilent , data: recvBuf endl;// 将接收到的数据原封不动地发给客户端ret send(clientfd, recvBuf, strlen(recvBuf), 0);if (ret ! strlen(recvBuf)) {cout send data error endl;} else {cout send data to client successfully, data recvBuf endl;}} else {cout recv data error endl;}close(clientfd);}}// 关闭监听socketclose(listenfd);return 0;
}
客户端代码实现
#include iostream
#include sys/types.h
#include arpa/inet.h
#include unistd.h
#include string.h#define SERVER_ADDRESS 127.0.0.1
#define SERVER_PORT 3000
#define SEND_DATA helloworldusing namespace std;int main() {// 创建一个socketint clientfd socket(AF_INET, SOCK_STREAM, 0);if (clientfd -1) {cout create client socket error endl;return -1;}// 连接服务器struct sockaddr_in serveraddr;serveraddr.sin_family AF_INET;serveraddr.sin_addr.s_addr inet_addr(SERVER_ADDRESS);serveraddr.sin_port htons(SERVER_PORT);if (connect(clientfd, (struct sockaddr *) serveraddr, sizeof(serveraddr)) -1) {cout connect socket error endl;return -1;}// 向服务器发送数据int ret send(clientfd, SEND_DATA, strlen(SEND_DATA), 0);if (ret ! strlen(SEND_DATA)) {cout send data error endl;return -1;} else {cout send data to client successfully, data SEND_DATA endl;}// 从服务器拉取数据char recvBuf[32] {0};ret recv(clientfd, recvBuf, 32, 0);if (ret 0) {cout recv data to client successfully, data recvBuf endl;} else {cout recv data to client error endl;}// 关闭socketclose(clientfd);return 0;
}函数和结构讲解
sockaddr_in和sockaddr
在讲解套接字编程函数之前有必要对socket编程的两个不可或缺的结构体进行说明sockaddr 和 sockaddr_in 结构如下
struct sockaddr{__SOCKADDR_COMMON (sa_); /* Common data: address family and length. */char sa_data[14]; /* Address data. */};/* Structure describing an Internet socket address. */
struct sockaddr_in{__SOCKADDR_COMMON (sin_);in_port_t sin_port; /* Port number. */struct in_addr sin_addr; /* Internet address. *//* Pad to size of struct sockaddr. */unsigned char sin_zero[sizeof (struct sockaddr)- __SOCKADDR_COMMON_SIZE- sizeof (in_port_t)- sizeof (struct in_addr)];};由于历史的原因套接字函数中如connectbind等使用的参数类型大多是sockaddr类型的。而如今进行套接字编程的时候大都使用sockaddr_in进行套接字地址填充.因此这就要求对这些函数进行调用的时候都必须要讲套接字地址结构指针进行类型强制转换例如
struct sockaddr_in serv;bind(sockfd,(struct sockaddr *)serv,sizeof(serv));socket 创建一个socket连接
/* Create a new socket of type TYPE in domain DOMAIN, usingprotocol PROTOCOL. If PROTOCOL is zero, one is chosen automatically.Returns a file descriptor for the new socket, or -1 for errors. */
extern int socket (int __domain, int __type, int __protocol) __THROW; 向用户提供一个套接字即套接口描述文件字它是一个整数如同文件描述符一样是内核标识一个IO结构的索引。 一般传入参数是这样的
int clientfd socket(AF_INET, SOCK_STREAM, 0);__domain这个参数指定一个协议簇也往往被称为协议域。系统存在许多可以的协议簇常见有AF_INET──指定为IPv4协议AF_INET6──指定为IPv6AF_LOCAL──指定为UNIX 协议域。这里指网络层的协议 __type这个参数指定一个套接口的类型套接口可能的类型有SOCK_STREAM、SOCK_DGRAM、SOCK_SEQPACKET、SOCK_RAW等等它们分别表明字节流、数据报、有序分组、原始套接口。 __protocol指定相应的传输协议也就是诸如TCP或UDP协议等等系统针对每一个协议簇与类型提供了一个默认的协议我们通过把protocol设置为0来使用这个默认的值。这里指传输层的协议 返回值socket函数返回一个套接字即套接口描述字。如果出现错误它返回-1并设置errno为相应的值用户应该检测以判断出现什么错
返回值成功则返回0失败返回非0
bind 绑定地址以及端口号问题
在服务器端我们有这样一段代码
// 初始化服务器地址
struct sockaddr_in bindaddr;
bindaddr.sin_family AF_INET;
bindaddr.sin_addr.s_addr htonl(INADDR_ANY);
bindaddr.sin_port htons(3000);
if (bind(listenfd, (struct sockaddr *) bindaddr, sizeof(bindaddr)) -1) {cout bind listen socket error endl;return -1;
}函数参数解释
/* Give the socket FD the local address ADDR (which is LEN bytes long). */
extern int bind (int __fd, __CONST_SOCKADDR_ARG __addr, socklen_t __len)bind的地址我们使用了宏INADDR_ANY,这个宏定义如下
/* Address to accept any incoming messages. */
#define INADDR_ANY ((in_addr_t) 0x00000000)如果应用程序不关心bind绑定的IP则可以使用这个宏底层的协议栈服务会自动选择一个合适的IP地址这样在多个网卡机器上选择IP地址会变得简单。 如果只想在本机上进行访问bind函数地址可以使用本地回环地址 如果只想被局域网的内部机器访问那么bind函数地址可以使用局域网地址 如果希望被公网访问那么bind函数地址可以使用INADDR_ANY or 0.0.0.0
网络通信的基本逻辑是客户端连接服务器即从客户端的地址端口 连接到 服务器的地址端口上。 一般来说服务器的端口号是固定的而客户端的端口号是连接发起时由操作系统随机分配的并且不会分配被占用的端口。端口号是一个short类型的值其范围为0~65535. 如果将bind函数中的端口号设置为0那么操作系统会随机为程序分配一个可用的监听端口。一般来说服务程序不会这么做因为服务程序是要对外服务的必须让客户端知道确切的IP地址和端口号。 在特殊的应用中我们也可以在客户端程序以指定的端口号连接服务器与普通的流程相比就是在创建socket与发起connect之间多了一次bind操作
图1 不绑定 图2 绑定
其他相关函数可以到往期文章中查看 socket编程常见函数使用方法