雕塑网站源码,邯郸中材建设有限责任公司网站,安徽亳州建设厅网站,有哪些网站可以做设计竞标TCP/IP网络编程-C #xff08;上#xff09; 一、基于TCP的服务端/客户端1、server端代码2、client端代码3、socket() 函数3.1、函数原型3.2、参数解析3.2.1、协议族#xff08;domain参数#xff09;3.2.2、套接字类型#xff08;type参数#xff09;3.2.3、最终使用的协… TCP/IP网络编程-C 上 一、基于TCP的服务端/客户端1、server端代码2、client端代码3、socket() 函数3.1、函数原型3.2、参数解析3.2.1、协议族domain参数3.2.2、套接字类型type参数3.2.3、最终使用的协议protocol参数 4、struct sockaddr_in IPv4地址结构体4.1、结构体原型4.2、结构体成员分析4.3、struct sockaddr 结构体 5、字节序转换6、bind() 函数6.1、字符串IP与网络字节序互相转换6.1.1、inet_addr()6.1.2、 inet_aton()6.1.3、inet_ntoa() 6.2 向套接字分配网络地址bind()函数6.2.1、函数原型6.2.2、参数解析 7、listen() 函数 - 进入等待连接请求状态7.1 函数原型7.2 参数解析 8、accept() 函数 - 受理客户端连接请求8.1 函数原型8.2 参数解析 9、connect()函数 - 向服务端发送连接请求9.1 函数原型9.2 参数解析9.3 客户端地址信息在哪里 10、基于TCP的服务端、客户端实现字符串转换10.1 客户端代码实现10.2 服务端代码实现 二、基于UDP的服务端/客户端1、server端代码实现2、client端代码实现3、sendto() 函数 - 填写地址并传输数据的I/O函数3.1 函数原型3.2 参数解析3.3 UDP客户端地址分配 4、recvfrom() 函数 - 接收数据4.1 函数原型4.2 参数解析 5、存在数据边界的UDP套接字6、创建已连接UDP套接字6.1 已连接UDP client端代码实现 一、基于TCP的服务端/客户端
先给出server端和client端代码client向server发送请求server接受请求并回复client消息这里简单回复hello world!后面详细说明函数作用
1、server端代码
#include iostream
#include sys/socket.h
#include arpa/inet.h
#include string
#include unistd.h
#include string.hint main(void){int serv_sock socket(PF_INET, SOCK_STREAM, 0); if(serv_sock -1)std::coutsocket error\n;struct sockaddr_in serv_addr;memset(serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family AF_INET;serv_addr.sin_addr.s_addr htonl(INADDR_ANY);serv_addr.sin_port htons(8080);if(bind(serv_sock, (struct sockaddr*) serv_addr, sizeof(serv_addr)) -1)std::coutbind error\n;if(listen(serv_sock, 3) -1)std::coutlisten error\n;struct sockaddr_in clie_addr;memset(clie_addr, 0, sizeof(clie_addr));socklen_t clie_addr_size 0;int clie_sock accept(serv_sock, (struct sockaddr*) clie_addr, clie_addr_size);if(clie_sock -1)std::coutaccept error\n;std::string message hello world!;send(clie_sock, message.c_str(), message.size(), 0);close(clie_sock);close(serv_sock);return 0;
}2、client端代码
#include iostream
#include sys/socket.h
#include arpa/inet.h
#include string
#include unistd.h
#include string.hint main(void){int clie_sock socket(PF_INET, SOCK_STREAM, 0);if(clie_sock -1)std::coutsocket error\n;std::string server_ip 127.0.0.1;struct sockaddr_in serv_addr;memset(serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family AF_INET;serv_addr.sin_addr.s_addr inet_addr(server_ip.c_str());serv_addr.sin_port htons(8080);if(connect(clie_sock, (struct sockaddr*)serv_addr, sizeof(serv_addr)) -1)std::coutconnect error\n;char message[30] {0};if(recv(clie_sock, message,30, 0) -1)std::coutread error\n;std::string str_message(message);std::coutmessage : str_message\n;close(clie_sock);return 0;
}
可以大概看看不理解没关系接下来会解释每一个函数的作用
3、socket() 函数
3.1、函数原型 socket(int domain, int type, int protocol); // 成功会返回文件描述符失败返回-13.2、参数解析
domain-协议族套接字中使用的协议族信息。type-套接字类型套接字数据传输类型信息。protocol-最终使用的协议计算机间通信中使用的协议信息。
3.2.1、协议族domain参数
协议族就是协议的分类信息在sys/socket.h中声明的协议分类信息如下表
名称协议族PF_INETIPv4互联网协议族PF_INET6IPv6互联网协议族PF_LOCAL本地通信的UNIX协议族PF_PACKET底层套接字的协议族PF_IPXIPX Novell协议族
3.2.2、套接字类型type参数
套接字类型指的是套接字数据传输方式这里介绍两种类型面向连接的套接字TCP 、面向消息的套接字UDP1、SOCK_STREAM表示使用面向连接的套接字提供可靠的、按序传递的、基于自己的服务。2、SOCK_DGRAM表示使用面向消息的套接字提供不可靠的、不按序传递的、以数据高速传输为目的的服务。
3.2.3、最终使用的协议protocol参数
最终使用的协议指的是同一协议族中存在多个数据传输方式相同的协议在同一协议族中数据传输方式相同但使用的协议不同一般该参数传递0即可。
4、struct sockaddr_in IPv4地址结构体
4.1、结构体原型
struct sockaddr_in
{sa_family_t sin_family; // 地址族uint16_t sin_port; // 16位TCP/UDP端口号struct in_addr sin_addr; // 32位IP地址char sin_zero[8]; // 不使用
}
// struct in_addr原型
struct in_addr
{In_addr_t s_addr; // 32位IP地址
}4.2、结构体成员分析
sin_family每种协议使用的地址族均不同比如IPv4使用4字节地址族IPv6使用16字节地址族。常用的地址族如下表
地址族含义AF_INETIPv4网络中使用的地址族AF_INET6IPv6网络中使用的地址族AF_LOCAL本地通信中采用的UNIX协议的地址族
sin_port保存16位端口号sin_addr保存32位IP地址信息sin_zero没有特殊含义为了使得sockaddr_in与sockaddr大小保持一致而插入的成员sockaddr后面介绍填充位0即可
4.3、struct sockaddr 结构体
在调用bind()函数时是将sockaddr_in转换为sockaddr进行传入的因为bind()函数要求的参数类型是sockaddr但为什么不直接定义sockaddr进行传入呢而是定义sockaddr_in转换为sockaddr呢如下sockaddr结构体原型
struct sockaddr
{sa_family_t sin_family; // 地址族char sa_data[14]; //地址信息
}因为最终IP地址信息和端口号会保存到sa_data成员中直接想sa_data中填写信息比较麻烦所以就有了sockaddr_in使用起了更方便。
5、字节序转换
由于CPU存储数据分为大端序和小端序但是网络数据传输中同一使用大端序所以下面介绍字节序转换函数1uint32_t ntohl (uint32_t __netlong)将uint32_t类型数据从网络字节序转换为主机字节序2uint16_t ntohs (uint16_t __netshort)将uint16_t类型数据从网络字节序转换为主机字节序3uint32_t htonl (uint32_t __hostlong)将uint32_t类型数据从主机字节序转换为网络字节序4uint16_t htons (uint16_t __hostshort)将uint16_t类型数据从主机字节序转换为网络字节序只需要在想sockaddr_in结构体填充数据时需要进行转换其它情况不需要转换字节序这都是自动完成的。当然sockaddr_in中的sin_family也不需要进行字节序转换因为sin_family 并不会发送到网络上详细信息自行了解。
6、bind() 函数
6.1、字符串IP与网络字节序互相转换
6.1.1、inet_addr()
作用将字符串类型IP转换为转换为整数并返回示例如下
#include arpa/inet.h
#include string
int main(void)
{std::string str_ip 127.0.0.1;uint32_t u_ip inet_addr(str_ip.c_str()); // 返回的结果u_ip可以直接赋值给sockaddr_in中的sin_addr.s_addrif(u_ip INADDR_NONE){std::cout无效ipstd::endl;}return 0;
}6.1.2、 inet_aton()
作用将字符串类型IP转换为转换为整数同时将结果直接写入sockaddr_in中的sin_addr调用成功返回true1失败返回false0示例如下
#include arpa/inet.h
#include string
int main(void)
{std::string str_ip 127.0.0.1;struct sockaddr_in addr;int ret inet_aton(str_ip.c_str(), addr.sin_addr); if(ret 0){std::cout无效ipstd::endl;}return 0;
}6.1.3、inet_ntoa()
作用将网络字节序的IP转换为字符串成功是返回字符串IP失败是返回-1示例如下
#include arpa/inet.h
#include string
int main(void)
{struct sockaddr_in addr;addr.sin_addr.s_addr htonl(0x1020304);std::string str_ip inet_ntoa(addr.sin_addr); std::coutstr_ipstd::endl;return 0;
}6.2 向套接字分配网络地址bind()函数
6.2.1、函数原型
int bind (int __fd, const struct sockaddr * __addr, socklen_t __len);
// 成功返回0失败返回-16.2.2、参数解析
参数参数说明__fd要分配地址信息的套接字文件描述符socket()函数返回的文件描述符__addrstruct sockaddr_in IPv4地址结构信息__len参数2的长度
7、listen() 函数 - 进入等待连接请求状态
7.1 函数原型 int listen(int sock, int backlog);// 成功时返回0 失败时返回-17.2 参数解析
参数参数说明sock套接字文件描述符backlog连接请求等待队列的长度若为5则队列长度为5表示最多使5个连接请求进入队列
所谓的等待连接请求状态是指客户端请求连接时在受理连接前一直使请求处于等待连接状态
8、accept() 函数 - 受理客户端连接请求
8.1 函数原型 int accept(int sock, struct sockaddr* addr, socklen_t* addrlen);// 成功时返回套接字文件描述符失败时返回-18.2 参数解析
参数参数说明sock套接字文件描述符addr保存发起连接请求的客户端地址信息addrlen保存第二个参数的地址长度
9、connect()函数 - 向服务端发送连接请求
9.1 函数原型 int connect(int sock, struct sockaddr* servaddr, socklen_t* addrlen);// 成功时返回0失败时返回-19.2 参数解析
参数参数说明sock客户端套接字文件描述符servaddr目标服务器端地址信息addrlen第二个参数的长度
9.3 客户端地址信息在哪里
客户端调用 connect() 函数函数后发生一下两种情况才会完成函数调用 1、服务端接收到连接请求 2、发生断网等异常情况而中断连接请求需要注意的是连接请求并不意味着服务端调用 accept() 函数其实是服务端把连接请求信息记录在等待队列中因此 connect() 函数返回后并不立即进行数据交换。实现服务端必经过程之一就是给套接字分配IP和端口号但在客户端实现过程中并没有为套接字分配IP和端口号而是在创建套接字后就立即调用了connect()函数。难道客户端套接字不需要IP和端口号吗当然不是学过计算机网络都知道网络中数据交换必须要IP和端口号既然如此那客户端套接字何时何地如何分配地址呢 1、何时在调用connect()函数时 2、何地操作系统内核中自动完成 3、如何IP使用主机的IP端口随机分配因此客户端IP和端口号是在调用connect()函数时自动分配无需使用bind()函数进行分配。
10、基于TCP的服务端、客户端实现字符串转换
我们已经学完了基础函数前面我们给出的示例是很简单的调用流程即客户端发送请求服务端接受请求并发送了“hello world”的信息然后各自关闭了连接。那服务端如何循环的接收并处理客户端的请求呢客户端如何不断的向服务端发送请求呢接下里我们实现一个简单的字符串转换功能即客户端向服务端发送字符串服务端接收到字符串后将字符串中大写字符转换为小写并返回给客户端。
10.1 客户端代码实现
#include iostream
#include sys/socket.h
#include arpa/inet.h
#include string
#include unistd.h
#include string.h
const int BUFFER_SIZE 128;int main(void){int clie_sock socket(PF_INET, SOCK_STREAM, 0);if(clie_sock -1)std::coutsocket error\n;std::string server_ip 127.0.0.1;struct sockaddr_in serv_addr;memset(serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family AF_INET;serv_addr.sin_addr.s_addr inet_addr(server_ip.c_str());serv_addr.sin_port htons(8080);if(connect(clie_sock, (struct sockaddr*)serv_addr, sizeof(serv_addr)) -1)std::coutconnect error\n;while(true){std::string str_input ;std::coutplease input:;std::cinstr_input;if(str_input q)break;send(clie_sock, str_input.c_str(), str_input.size(), 0);char message[BUFFER_SIZE] {0};int recv_size 0;if((recv_size recv(clie_sock, message, BUFFER_SIZE - 1, 0)) -1)std::coutread error\n;message[recv_size] \0;std::string str_message(message, recv_size);std::coutbefore:str_input\n;std::coutlater:str_message\n;}close(clie_sock);return 0;
}
10.2 服务端代码实现
#include iostream
#include sys/socket.h
#include arpa/inet.h
#include string
#include unistd.h
#include string.h
#include regex
#include cctype
const int BUFFER_SIZE 128;void to_lower(const std::string str_input, std::string str_output){std::regex pattern(^[a-zA-Z]$);bool is_letters std::regex_match(str_input, pattern);if(is_letters){str_output.resize(str_input.size());std::transform(str_input.begin(), str_input.end(), str_output.begin(), ::tolower);}else{str_output 包含其它字符转换失败;}return;
}int main(void){int serv_sock socket(PF_INET, SOCK_STREAM, 0); if(serv_sock -1)std::coutsocket error\n;struct sockaddr_in serv_addr;memset(serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family AF_INET;serv_addr.sin_addr.s_addr htonl(INADDR_ANY);serv_addr.sin_port htons(8080);if(bind(serv_sock, (struct sockaddr*) serv_addr, sizeof(serv_addr)) -1)std::coutbind error\n;if(listen(serv_sock, 3) -1)std::coutlisten error\n;struct sockaddr_in clie_addr;memset(clie_addr, 0, sizeof(clie_addr));socklen_t clie_addr_size 0;int clie_sock 0;clie_sock accept(serv_sock, (struct sockaddr*) clie_addr, clie_addr_size);if(clie_sock -1)std::coutaccept error\n;while(true){char mess[BUFFER_SIZE] {0};int recv_size 0;if((recv_size recv(clie_sock, mess, BUFFER_SIZE - 1, 0)) -1) {std::coutrecv error\n;}mess[recv_size] \0;std::string str_message(mess, recv_size);std::string str_result ;to_lower(str_message, str_result);send(clie_sock, str_result.c_str(), str_result.size(), 0);}close(clie_sock);close(serv_sock);return 0;
}二、基于UDP的服务端/客户端
UDP服务端/客户端不像TCP那样在连接状态下交换数据因此与TCP不同无需经过连接过程。也就是说不必调用TCP连接过程中的listen()函数和accept()函数UPD只有 创建套接字 的过程和 数据交换 的过程UPD不同于TCP不存在请求连接和受理过程因此在某种意义上无法明确区分服务端和客户端只能因提供服务的一方称为服务端。下面使用UDP方式实现 字符串转换 的例子
1、server端代码实现
#include iostream
#include sys/socket.h
#include arpa/inet.h
#include string
#include unistd.h
#include string.h
#include regex
#include cctype
const int BUFFER_SIZE 128;void to_lower(const std::string str_input, std::string str_output){std::regex pattern(^[a-zA-Z]$);bool is_letters std::regex_match(str_input, pattern);if(is_letters){str_output.resize(str_input.size());std::transform(str_input.begin(), str_input.end(), str_output.begin(), ::tolower);}else{str_output 包含其它字符转换失败;}return;
}int main(void){int serv_sock socket(PF_INET, SOCK_DGRAM, 0); if(serv_sock -1)std::coutsocket error\n;struct sockaddr_in serv_addr;memset(serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family AF_INET;serv_addr.sin_addr.s_addr htonl(INADDR_ANY);serv_addr.sin_port htons(8080);if(bind(serv_sock, (struct sockaddr*) serv_addr, sizeof(serv_addr)) -1)std::coutbind error\n;struct sockaddr_in clie_addr;memset(clie_addr, 0, sizeof(clie_addr));socklen_t clie_addr_size 0;int clie_sock 0;while(true){char message[BUFFER_SIZE] {0};int mess_len recvfrom(serv_sock, message, BUFFER_SIZE, 0, (struct sockaddr*)clie_addr, clie_addr_size);message[mess_len] \0;std::string str_input(message, mess_len);std::string str_output ;to_lower(str_input, str_output);sendto(serv_sock, str_output.c_str(), str_output.size(), 0, (struct sockaddr*)clie_addr, clie_addr_size);}close(serv_sock);return 0;
}2、client端代码实现
#include iostream
#include sys/socket.h
#include arpa/inet.h
#include string
#include unistd.h
#include string.h
const int BUFFER_SIZE 128;int main(void){int clie_sock socket(PF_INET, SOCK_DGRAM, 0);if(clie_sock -1)std::coutsocket error\n;std::string server_ip 127.0.0.1;struct sockaddr_in serv_addr, from_addr;socklen_t from_addr_size 0;memset(serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family AF_INET;serv_addr.sin_addr.s_addr inet_addr(server_ip.c_str());serv_addr.sin_port htons(8080);while(true){std::string str_input ;std::coutplease input:;std::cinstr_input;if(str_input q)break;sendto(clie_sock, str_input.c_str(), str_input.size(), 0, (struct sockaddr*)serv_addr, sizeof(serv_addr));char message[BUFFER_SIZE] {0};int mess_len recvfrom(clie_sock, message, BUFFER_SIZE, 0, (struct sockaddr*)from_addr, from_addr_size);message[mess_len] \0;std::string str_message(message, mess_len);std::coutbefore:str_input\n;std::coutlater:str_message\n;}close(clie_sock);return 0;
}3、sendto() 函数 - 填写地址并传输数据的I/O函数
3.1 函数原型 ssize_t sendto(int sock, void* buff, size_t nbytes, int flags, struct sockaddr* to, socklen_t addrlen);// 成功返回传输的字节数失败返回-13.2 参数解析
参数参数解析sock用于传输数据的UDP套接字文件描述符buff将要传输数据的地址nbytes将要传输数据的长度以字节为单位flags可选项没有传递0即可to存有目标地址信息的sockaddr结构体变量addrlento参数的长度
3.3 UDP客户端地址分配
在TCP中客户端是通过connect()函数字符为客户端分配地址的那UDP中如何分配地址呢是在 sendto() 函数中进行地址分配的如果调用sendto()函数时发现没有分配地址信息则在首次调用时给相应套接字自动分配IP和端口号IP是主机IP端口号随机分配。而且此时分配的地址信息回一直保留到程序结束因此也可以与其它UDP进行数据交换。
4、recvfrom() 函数 - 接收数据
4.1 函数原型 ssize_t recvfrom(int sock, void* buff, size_t nbytes, int flags, sockaddr* from, socklen_t* addrlen);// 成功时返回接收的字节数失败返回-14.2 参数解析
参数参数解析sock用于接收数据的UDP套接字文件描述符buff接受到数据的缓冲地址nbytes可接收的最大字节数不能超过buff所指的缓冲大小flags可选项没有传递0即可from保存发送端的地址信息addrlen保存from参数长度的变量的地址值
5、存在数据边界的UDP套接字
UDP是具有数据边界的协议在传输过程中调用I/O函数的次数非常重要。因此输入函数和输出函数的调用次数应该完全一致这样才能保证接收全部已发送数据。比如客户端调用了3次sendto()函数那么服务端也必须调用3次recvfrom()函数才可以接收客户端3次发送的信息。这和TCP不同TCP客户端调用3次send()函数那么服务端可以只调用1次recv()函数就可以接收3次发送的信息。
6、创建已连接UDP套接字
TCP套接字中需要注册待传输数据的目标IP和端口号而UDP中则无需注册因此通过sendto()函数传输数据的过程大致分为3个阶段 1、第一阶段向UDP套接字注册目标IP和端口号 2、第二阶段传输数据 3、第三阶段删除UDP套接字中注册的目标地址信息每次调用sendto函数时重复上述过程。每次都变更目标地址因此可以重复利用同一UDP套接字向不同目标传输数据。这种未注册目标地址信息的套接字称为未连接套接字反之注册了目标地址的套接字称为连接connected套接字。显然UDP套接字默认属于未连接套接字。但UDP套接字在下述情况下显得不太合理: “IP为211.210.147.82的主机82号端口共准备了3个数据,调用3次sendto函数进行传输。”此时需重复3次上述三阶段。因此要与同一主机进行长时间通信时将UDP套接字变成已连接套接字会提高效率。上述三个阶段中第一个和第三个阶段占整个通信过程近1/3的时间缩短这部分时间将大大提高整体性能。创建已连接UDP套接字的过程格外简单只需针对UDP套接字调用connect()函数即可当然针对UDP套接字调用connect()函数并不意味着要与对方套接字连接这只是向UDP套接字注册目标IP和端口号。之后每次调用sendto()函数只需要传输数据因为已经指定了收发对象所以此时不仅可以使用sendto()、recvfrom()函数还可以使用send()和recv()函数。下面给出已连接UDP套接字client端代码实现服务端没有变化。
6.1 已连接UDP client端代码实现
#include iostream
#include sys/socket.h
#include arpa/inet.h
#include string
#include unistd.h
#include string.h
const int BUFFER_SIZE 128;int main(void){int clie_sock socket(PF_INET, SOCK_DGRAM, 0);if(clie_sock -1)std::coutsocket error\n;std::string server_ip 127.0.0.1;struct sockaddr_in serv_addr, from_addr;socklen_t from_addr_size 0;memset(serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family AF_INET;serv_addr.sin_addr.s_addr inet_addr(server_ip.c_str());serv_addr.sin_port htons(8080);if(connect(clie_sock, (struct sockaddr*)serv_addr, sizeof(serv_addr)) -1)std::coutconnect error\n;while(true){std::string str_input ;std::coutplease input:;std::cinstr_input;if(str_input q)break;// sendto(clie_sock, str_input.c_str(), str_input.size(), 0, (struct sockaddr*)serv_addr, sizeof(serv_addr));send(clie_sock, str_input.c_str(), str_input.size(), 0);char message[BUFFER_SIZE] {0};// int mess_len recvfrom(clie_sock, message, BUFFER_SIZE, 0, (struct sockaddr*)from_addr, from_addr_size);int mess_len recv(clie_sock, message, BUFFER_SIZE - 1, 0);message[mess_len] \0;std::string str_message(message, mess_len);std::coutbefore:str_input\n;std::coutlater:str_message\n;}close(clie_sock);return 0;
}已连接UDP客户端比未连接UDP客户端只多了connect()函数调用而且已连接UDP客户端中还可以使用send()和recv()函数进行数据交换其它部分并没有什么不同。