建设资格执业注册中心网站,上海2023年建管平台放假时间,使用腾讯云建设网站教程,建设网站的计划表一.网络准备
1.套接字
在TCP/IP 协议中#xff0c;“ip 地址TCP 或UDP 端口号”唯一标识网络通讯中的一个进程。“IP 地址端口号”就对应一个socket。欲建立连接的两个进程各自有一个 socket 来标识#xff0c;那么这两个 socket 组成的 socket pair 就唯一标识一个连接。因…一.网络准备
1.套接字
在TCP/IP 协议中“ip 地址TCP 或UDP 端口号”唯一标识网络通讯中的一个进程。“IP 地址端口号”就对应一个socket。欲建立连接的两个进程各自有一个 socket 来标识那么这两个 socket 组成的 socket pair 就唯一标识一个连接。因此可以用 socket 来描述网络连接的一对一关系。 简单来说用数学里面的坐标系类比一下IP地址端口号表示一个进程即一个套接字两个套接字连线表示通信。
一个文件描述符指向一个套接字(该套接字内部由内核借助两个缓冲区实现)。在通信过程中 套接字一定是成对出现的。
2.网络字节序列
计算机主机对于多字节序列采用的是小端法存储而网络通信的多字节序列是大端法存储的因此在进行网络通信时主机把数据送到客户端的发送缓冲区前需要把数据进行网络字节转换同样的接受主机在服务器的接收端取出数据后又要把数据转化成主机字节序列。
#include arpa/inet.huint32_t htonl(uint32_t hostlong); //本地---》网络IP地址
uint16_t htons(uint16_t hostshort); //本地---》网络端口
uint32_t ntohl(uint32_t netlong); //网络---》本地IP地址
uint16_t ntohs(uint16_t netshort); //网络---》本地端口解释
h表示本地主机hostto表示变换n表示网络netl表示IP地址IP地址用32位表示s表示端口 注意我们平时用点分十进制表示IP地址所以要想进行网络字节转换先要使用atoi把string转化成int 3.IP地址转换函数
为了直接从点分十进制进行转化成网络字节序所以有了IP地址转换函数。
#include arpa/inet.hint inet_pton(int af, const char *src, void *dst); //IP地址---》网络解释
af表示版本协议号只能取AF_INET表示IPv4或AF_INET6表示IPv6src传入的IP地址点分十进制string类型dst传出的转换后的IP地址网络字节序返回值1表示成功0表示异常说明传入的src不是一个IP地址-1表示失败
#include arpa/inet.hconst char *inet_ntop(int af, const void *src, char *dst, socklen_t size); //网络---》IP地址解释
src传入的转换后的IP地址网络字节序dst传出的IP地址点分十进制string类型sizedst缓冲区的大小返回值成功返回dst失败返回NULL
4.sockaddr数据结构
直接说结论后面的bind()函数的参数要用到strcut sockaddr这种结构体的指针但是现在IPv4使用的结构体普遍是strcut sockaddr_in所以现在的使用方式是我们一般先定义strcut sockaddr_in的结构体使用的时候进行强转。比如
struct sockaddr_in servaddr;
bind(listen_fd, (struct sockaddr *)servaddr, sizeof(servaddr)); 其余不要管重点看bind()的第二个参数。
所以现在我们重点学习一下strcut sockaddr_in结构体
struct sockaddr_in {__kernel_sa_family_t sin_family; //地址结构类型__be16 sin_port; //端口号(网络字节序)struct in_addr sin_addr; //IP地址网络字节序
};struct in_addr { __be32 s_addr; //IP地址网络字节序
};注意这里的sockaddr_in的第三个参数是一个结构体的形式所以赋值时需要注意例如
struct sockaddr_in addr;
addr.sin_family AF_INET/AF_INET6;
addr.sin_port htons(9527);
int dst;
inet_pton(AF_INET,192.157.22.45,(void *)dst);
addr.sin_addr.s_addr dst;这里使用强转把dst变成void *类型赋值是不要忘了.s_addr进入对应结构体内部赋值。 一般的时候我们用的是本地主机写代码所以可以将 int dst;
inet_pton(AF_INET,192.157.22.45,(void *)dst);
addr.sin_addr.s_addr dst;写成 addr.sin_addr.s_addr htonl(INADDR_ANY);这里的INADDR_ANY是一种宏表示取出系统中有效的任意IP地址但是是二进制类型。 二.网络套接字函数
1.socket模型 socket()创建套接字bind()绑定服务器的IP和端口listen()设置同时监听上限accept()阻塞监听客户端连接connect()客户端绑定IP和端口进行连接 注意在一个模型中其实有三个socket套接字其中服务器刚开始的套接字在accept()函数里作为参数传入返回另一个套接字来与客户端进行真正的连接。 2.socket函数
函数原型
#include sys/socket.hint socket(int domain, int type, int protocol);domain用来指定传输协议AF_INET、AF_INET6、AF_UNIX表示本地协议使用在Unix和Linux系统上一般都是当客户端和服务器在同一台及其上的时候使用type用来指定协议类型可以取SOCK_STREAM表示流式协议或SOCK_DGRAM表示报式协议protocol传0表示默认协议 type为SOCK_STREAM且protocol0表示使用TCP传输type为SOCK_DGRAM且protocol0表示使用UDP传输。 3.bind函数
作用给socket绑定一个地址结构IP地址端口
#include sys/socket.hint bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);sockfdsocket文件描述符一般取上面socket()函数的返回值*addr上面介绍过用来指定地址结构信息addrlen地址结构大小总是取sizeof(addr)
4.listen函数
作用设置同时与服务器建立连接的客户端数量
#include sys/socket.hint listen(int sockfd, int backlog);sockfdsocket文件描述符一般取上面socket()函数的返回值backlog连接上限最大为128
5.accept函数
作用阻塞等待客户端连接成功的话返回一个成功与客户端连接的socket文件描述符
#include sys/socket.hint accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);sockdf socket文件描述符一般取上面socket()函数的返回值相当于把开始的socket传入addr传出参数返回成功链接客户端地址信息含IP地址和端口号addrlen传入传出参数值-结果传入sizeof(addr)大小函数返回时返回真正接收到地址结构体的大小返回值 成功返回一个新的socket文件描述符用于和客户端通信失败返回-1设置errno
6.connect函数
作用使用现有的socket与服务器建立连接
#include sys/socket.hint connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);sockfdsocket文件描述符一般取上面socket()函数的返回值*addr服务器的地址结构用来连接addrlen地址结构大小总是取sizeof(addr)
7.read函数
read函数用于从socket中读取数据。它的原型如下
ssize_t read(int fd, void *buffer, size_t count);fd是socket的文件描述符用于标识一个打开的socket。buffer是一个指针指向一个缓冲区该缓冲区用于存储从socket读取的数据。count指定了buffer的大小即希望读取的最大字节数。
read函数返回实际读取的字节数。如果read返回0表示连接已经关闭。如果返回-1表示发生了错误此时可以通过errno变量查看错误类型。
8.write函数
write函数用于向socket写入数据。它的原型如下
ssize_t write(int fd, const void *buffer, size_t count);fd同样是socket的文件描述符。buffer是一个指针指向包含要发送数据的缓冲区。count指定了要发送的字节数。write函数返回实际写入的字节数。如果返回-1表示发生了错误同样可以通过errno变量查看错误类型。
8.文件描述符
fd_set 是一个数据类型用于在 select 系统调用中表示一组文件描述符。以下是与 fd_set 相关的一些常用宏函数它们用于操作 fd_set
FD_ZERO: 作用将 fd_set 清零即初始化 fd_set使其不包含任何文件描述符。 使用方法FD_ZERO(fdset);FD_SET: 作用将一个文件描述符添加到 fd_set 中。 使用方法FD_SET(fd, fdset); 其中 fd 是要添加的文件描述符fdset 是 fd_set 的实例。FD_CLR: 作用从 fd_set 中移除一个文件描述符。 使用方法FD_CLR(fd, fdset); 其中 fd 是要移除的文件描述符fdset 是 fd_set 的实例。FD_ISSET: 作用测试一个文件描述符是否在 fd_set 中。 使用方法FD_ISSET(fd, fdset); 如果 fd 在 fdset 中则返回非零值否则返回0。
三.实现一个简单的通信
1.通信流程分析
TCP通信流程分析: server:
1. socket() 创建socket
2. bind() 绑定服务器地址结构
3. listen() 设置监听上限
4. accept() 阻塞监听客户端连接
5. read(fd) 读socket获取客户端数据
6. 小--大写 toupper()
7. write(fd)
8. close(); client:
1. socket() 创建socket
2. connect(); 与服务器建立连接
3. write() 写数据到 socket
4. read() 读转换后的数据。
5. 显示读取结果
6. close()2.实现服务器
#include stdio.h
#include stdlib.h
#include string.h
#include unistd.h
#include sys/socket.h
#include netinet/in.h
#include arpa/inet.h
#include ctype.h#define ser_port 9527
int main()
{//1.socket partint sfd0,cfd0;sfd socket(AF_INET, SOCK_STREAM, 0);if(sfd-1){printf(socket error\n);}//2.bind partstruct sockaddr_in ser_addr,cet_addr;ser_addr.sin_familyAF_INET;ser_addr.sin_porthtons(ser_port);ser_addr.sin_addr.s_addr htonl(INADDR_ANY);bind(sfd, (struct sockaddr *)ser_addr, sizeof(ser_addr));//3.listen partlisten(sfd,20);//4.accept partchar client_ip[BUFSIZ];socklen_t cet_addr_len sizeof(cet_addr);cfd accept(sfd,(struct sockaddr *)cet_addr,cet_addr_len);if(cfd-1){printf(accept socket error\n);}//5.read partchar buf[BUFSIZ];while(true){int ret read(cfd,buf,sizeof(buf));write(STDOUT_FILENO,buf,ret);for(int i0;iret;i){buf[i]toupper(buf[i]);}write(cfd,buf,ret);}//6.close partclose(sfd);close(cfd);return 0;
}
3.实现客户端
#include stdio.h
#include stdlib.h
#include string.h
#include unistd.h
#include sys/socket.h
#include netinet/in.h
#include netinet/in.h
#include arpa/inet.h#define ser_port 9527
int main() {//1.socket partint hfd0;hfdsocket(AF_INET, SOCK_STREAM, 0);if(hfd-1){printf(socket error\n);}//2.connect partstruct sockaddr_in cil_addr;cil_addr.sin_familyAF_INET;cil_addr.sin_porthtons(ser_port);inet_pton(AF_INET,192.168.242.128,cil_addr.sin_addr.s_addr);connect(hfd,(struct sockaddr *)cil_addr,sizeof(cil_addr));// 3. Write and Read partchar buf[BUFSIZ];int count 10;while (count-- 0) {if (write(hfd, hello, 5) -1) {perror(write);close(hfd);exit(EXIT_FAILURE);}int ret read(hfd, buf, sizeof(buf));if (ret -1) {perror(read);close(hfd);exit(EXIT_FAILURE);} else if (ret 0) {printf(Server disconnected\n);break;}write(STDOUT_FILENO, buf, ret);}close(hfd);return 0;
}四.出错封装函数思想
1.封装思想
上面简单的写了一个实现大小写转换的通信服务器和客户端但是并不完善里面的很多函数调用都没有做错误处理在实际编写过程中并不规范。
我们以accept()函数为例在上面的例子中我们是这样写的 char client_ip[BUFSIZ];socklen_t cet_addr_len sizeof(cet_addr);cfd accept(sfd,(struct sockaddr *)cet_addr,cet_addr_len);if(cfd-1){printf(accept socket error\n);}但是实际过程中的main.cpp代码量其实是很少的我们可以把accept()函数进行封装成类似自定义函数存储在另一个源文件中这样就可以在main.cpp调用自定义的Accept()函数啦。
一般这种错误函数的封装我们把重新自定义的函数名取为原函数名基础上首字母大写然后在自定义函数内部实现错误封装所以上述代码可以改成
int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr)
{int n;again:if ( (n accept(fd, sa, salenptr)) 0) {if ((errno ECONNABORTED) || (errno EINTR))goto again;elseperr_exit(accept error);}return n;
}注意自定义函数封装在wrcp.cpp文件中不要忘了对应的wrcp.h头文件。
2.读写函数
我们在C语言文件操作时学习了一些读写函数但是强调过只有read()和write()是满足系统调用的即在socket通信过程中使用所以平时的读写我们要自定义一些读写n字节函数或读写n行函数。下面代码仅供参考
ssize_t Readn(int fd, void *vptr, size_t n)
{size_t nleft;ssize_t nread;char *ptr;ptr vptr;nleft n;while (nleft 0) {if ( (nread read(fd, ptr, nleft)) 0) {if (errno EINTR)nread 0;elsereturn -1;} else if (nread 0)break;nleft - nread;ptr nread;}return n - nleft;
}ssize_t Writen(int fd, const void *vptr, size_t n)
{size_t nleft;ssize_t nwritten;const char *ptr;ptr vptr;nleft n;while (nleft 0) {if ( (nwritten write(fd, ptr, nleft)) 0) {if (nwritten 0 errno EINTR)nwritten 0;elsereturn -1;}nleft - nwritten;ptr nwritten;}return n;
}static ssize_t my_read(int fd, char *ptr)
{static int read_cnt;static char *read_ptr;static char read_buf[100];if (read_cnt 0) {
again:if ((read_cnt read(fd, read_buf, sizeof(read_buf))) 0) {if (errno EINTR)goto again;return -1; } else if (read_cnt 0)return 0;read_ptr read_buf;}read_cnt--;*ptr *read_ptr;return 1;
}ssize_t Readline(int fd, void *vptr, size_t maxlen)
{ssize_t n, rc;char c, *ptr;ptr vptr;for (n 1; n maxlen; n) {if ( (rc my_read(fd, c)) 1) {*ptr c;if (c \n)break;} else if (rc 0) {*ptr 0;return n - 1;} elsereturn -1;}*ptr 0;return n;
}