当前位置: 首页 > news >正文

Linux网络编程(上)

一、基础

网络套接字:socket一个文件描述符指向一个套接字。套接字是成对出现的。(插头和插座)网络字节序:小端法:(PC本地存储) 高位存高地址。低位存低地址。大端法:(网络存储)高位存低地址。低位存高地址。// 二进制左边是高位
int a = 0x12345678小端法:
地址0x00: 0x78
地址0x01: 0x56
地址0x02: 0x34
地址0x03: 0x12大端法:
地址0x00: 0x12
地址0x01: 0x34
地址0x02: 0x56
地址0x03: 0x78

二、IP地址转换函数

htonl: 本地 --> 网络(IP)
htons: 本地 --> 网络(port)
ntohl: 网络 --> 本地(IP)
ntohs: 网络 --> 本地(Port)本地字节序(string IP)---> 网络字节序
int inet_pton(int af, const char *src, void *dst)  af:AF_FNET、AF_INET6src:传入,IP地址(点分十进制)dst:传出,转换后的网络字节序的IP地址。
返回值:成功:1异常:0,说明src指向的不是一个有效的ip地址。失败:-1网络字节序 --> 本地字节序(string IP)
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);af: AF_INET、AF_INET6src:网络字节序IP地址dst:本地字节序(string IP)size: dst 的大小。
返回值:成功:dst。失败:NULL

三、sockaddr结构

struct sockaddr_in addr:addr.sin_family = AF_INET/AF_INET6addr.sin_port = htons(9527);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) 取出系统中有效的任意IP地址。二进制类型。bind(fd, (struct sockaddr *)&addr, size):

四、socket和bind函数

创建一个套接字:
int socket(int domain, int type,int protocol);domain: AF_INET、AF_INET6、AF_UNIXtype: SOCK_STREAI、SOCK_DGRAMprotocol:0
返回值:成功:新套接字所对应文件描述符失败:-1 errno给socket绑定一个地址结构(IP+port):
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)sockfd:socket函数返回值struct sockaddr_in addr:addr.sin_family = AF_INET;addr.sin_port = htons(8888);addr.sin_addr.s_addr = htonl(INADDR_ANY):addr: (struct sockaddr *)&addraddrlen:sizeof(addr)地址结构的大小。
返回值:成功:0失败:-1errno

五、listen和accept函数

设置同时与服务器建立连接的上限数。(同时进行3次握手的客户端数量)
int listen(int sockfd, int backlog);sockfd:socket函数返回值backlog:上限数值。最大值128.
返回值:成功:0失败:-1 errno阻塞等待客户端建立连接
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);sockfd:socket函数返回值addr:传出参数。成功与服务器建立连接的那个客户端的地址结构(IP+port)socklen_t clit_addr_len = sizeof(addr):addrlen:传入传出。&clit_addr_len入:addr的大小。出:客户端addr实际大小。
返回值:成功:能与服务器进行数据通信的socket对应的文件描述。失败:-1,errno

六、connect函数

用现有的socket与服务器建立连接
int connect (int sockfd, const struct sockaddr* addr, socklen_t addrlen);sockfd: socket函数返回值addr:传入参数。服务器的地址结构addrlen:服务器的地址结构的大小
返回值。成功:0失败:-1 errno
如果不使用bind绑定客户端地址结构,采用"隐式绑定".

七、TCP实现客户端服务器连接

// 服务器代码
#include<stdio.h>
#include<ctype.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>#define SERV_PORT 9527void sys_err(const char* str) {perror(str);exit(1);
}int main(int argc, char* argv[]) {int lfd = 0, cfd = 0;int ret, i;char buf[BUFSIZ], client_IP[1024];struct sockaddr_in serv_addr, clit_addr;socklen_t clit_addr_len;serv_addr.sin_family = AF_INET;serv_addr.sin_port = htons(SERV_PORT);serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);lfd = socket(AF_INET, SOCK_STREAM, 0);if (lfd == -1) {sys_err("socket error");}bind(lfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));listen(lfd, 128);clit_addr_len = sizeof(clit_addr);cfd = accept(lfd, (struct sockaddr*)&clit_addr, &clit_addr_len);if (cfd == -1) {sys_err("accept error");}printf("client ip:%s port:%d\n",inet_ntop(AF_INET, &clit_addr.sin_addr.s_addr, client_IP, sizeof(client_IP)),ntohs(clit_addr.sin_port));while (1) {ret = read(cfd, buf, sizeof(buf));write(STDOUT_FILENO, buf, ret);for (i = 0; i < ret; i++) {buf[i] = toupper(buf[i]);}write(cfd, buf, ret);}close(lfd);close(cfd);return 0;
}// 测试如下图

// 客户端代码
#include<stdio.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>#define SERV_PORT 9527void sys_err(const char* str) {perror(str);exit(1);
}int main(int argc, char* argv[]) {int cfd;int conter = 10;char buf[BUFSIZ];struct sockaddr_in serv_addr;	//服务器地址结构serv_addr.sin_family = AF_INET;serv_addr.sin_port = htons(SERV_PORT);inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr.s_addr);cfd = socket(AF_INET, SOCK_STREAM, 0);if (cfd == -1) {sys_err("socket error");}int ret = connect(cfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));if (ret != 0) {sys_err("connect error");}while(--conter) {write(cfd, "hello\n", 6);ret = read(cfd, buf, sizeof(buf));write(STDOUT_FILENO, buf, ret);sleep(1);}close(cfd);return 0;
}


八、图解C-S模型


九、封装错误处理代码

将一系列自己写的函数和错误处理代码封装到另一个程序中
// 服务器代码
#include"wrap.h"#define SERV_PORT 9527int main(int argc, char* argv[]) {int lfd = 0, cfd = 0;int ret, i;char buf[BUFSIZ], client_IP[1024];struct sockaddr_in serv_addr, clit_addr;socklen_t clit_addr_len;serv_addr.sin_family = AF_INET;serv_addr.sin_port = htons(SERV_PORT);serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);lfd = Socket(AF_INET, SOCK_STREAM, 0);    // 调用自己写的函数ret = bind(lfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));if(ret != 0){sys_err("bind error");}Listen(lfd, 128);    // 调用自己写的函数// 此处省略...return 0;
}
// wrap.h
#ifndef _WRAP_H_
#define _WRAP_H_#include<stdio.h>
#include<ctype.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>void sys_err(const char* str);
int Socket(int domain, int type, int protocol);
int Listen(int sockfd, int backlog);#endif
// wrap.c
#include"wrap.h"void sys_err(const char* str) {perror(str);exit(1);
}int Socket(int domain, int type, int protocol) {int n = socket(domain, type, protocol);if (n = -1) {sys_err("socket error");return n;}return n;
}int Listen(int sockfd, int backlog) {int n = listen(sockfd, backlog);if (n = -1) {sys_err("listen error");return n;}return n;
}

十、多进程服务器实现

#include<stdio.h>
#include<ctype.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<string.h>
#include<strings.h>
#include<unistd.h>
#include<errno.h>
#include<signal.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<pthread.h>// 封装的错误处理函数
#include"wrap.h"#define SRV_PORT 9999void catch_child(int signum) {while ((waitpid(0, NULL, WNOHANG)) > 0);return;
}int main(int argc, char* argv[]) {int lfd, cfd;pid_t pid;struct sockaddr_in srv_addr, clt_addr;socklen_t clt_addr_len;char buf[BUFSIZ];int ret, i;bzero(&srv_addr, sizeof(srv_addr));		// 将地址结构清零srv_addr.sin_family = AF_INET;srv_addr.sin_port = htons(SRV_PORT);srv_addr.sin_addr.s_addr = htonl(INADDR_ANY);lfd = Socket(AF_INET, SOCK_STREAM, 0);Bind(lfd, (struct sockaddr*)&srv_addr, sizeof(srv_addr));Listen(lfd, 128);clt_addr_len = sizeof(clt_addr);while (1) {cfd = Accept(lfd, (struct sockaddr*)&clt_addr, &clt_addr_len);pid = fork();if (pid < 0) {sys_err("fork error");}else if (pid == 0) {close(lfd);break;}else {struct sigaction act;act.sa_handler = catch_child;sigemptyset(&act.sa_mask);act.sa_flags = 0;ret = sigaction(SIGCHLD, &act, NULL);if (ret != 0) {perr_exit("sigaction error");}close(cfd);continue;}}if (pid = 0) {while (1) {ret = Read(cfd, buf,sizeof(buf));if (ret = 0) {close(cfd);exit(1);}for (i = 0; i < ret; i++) {buf[i] = toupper(buf[i]);}write(cfd, buf, ret);write(STDOUT_FILENO, buf, ret);}}return 0;
}// 这样实现了多个客户端访问一个服务器,同时不会产生僵尸进程

十一、多线程服务器实现

#include<stdio.h>
#include<string.h>
#include<arpa/inet.h>
#include<pthread.h>
#include<ctype.h>
#include<unistd.h>
#include<fcntl.h>#include"wrap.h"#define MAXLINE 8192
#define SERV_PORT 8000struct s_info {		// 定义一个结构体,将地址结构跟cfd捆绑struct sockaddr_in cliaddr;int connfd;
};void* do_work(void* arg) {int n, i;struct s_info* ts = (struct s_info*)arg;char buf[MAXLINE];char str[INET_ADDRSTRLEN];				// #define INET_ADDRSTRLEN 16while (1) {n = Read(ts->connfd, buf, MAXLINE);	// 读客户端if (n == 0) {printf("the client %d closed...\n", ts->connfd);break;							// 跳出循环,关闭cfd}printf("received from %s at PORT %d\n",inet_ntop(AF_INET, &(*ts).cliaddr.sin_addr, str, sizeof(str)),ntohs((*ts).cliaddr.sin_port));		// 打印客户端信息(IP/PORT)for (i = 0; i < n; i++) {buf[i] = toupper(buf[i]);			// 小写 --> 大写}Write(STDOUT_FILENO, buf, n);	// 写出至屏幕Write(ts->connfd, buf, n);		// 回写给客户端}Close(ts->connfd);/*Close函数如下:int n = close(fd);if(n == -1){sys_err("close error");}return n;*/    return (void*)0;
}int main(void) {struct sockaddr_in servaddr, cliaddr;socklen_t cliaddr_len;int listenfd, connfd;pthread_t tid;struct s_info ts[256];		// 创建结构体数组.int i = 0;listenfd = Socket(AF_INET, SOCK_STREAM, 0);	// 创建一个socket,得到lfdbzero(&servaddr, sizeof(servaddr));			// 地址结构清零servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY);		// 指定本地任意IPservaddr.sin_port = htons(SERV_PORT);				// 指定端口号Bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr));Listen(listenfd, 128);		// 设置同一时刻链接服务器上限数printf("Accepting client connect ...\n");while (1) {cliaddr_len = sizeof(cliaddr);connfd = Accept(listenfd, (struct sockaddr*)&cliaddr, &cliaddr_len);	//阻塞监听客户端链接请求ts[i].cliaddr = cliaddr;ts[i].connfd = connfd;pthread_create(&tid, NULL, do_work, (void*)&ts[i]);pthread_detach(tid);	//子线程分离,防止僵线程产生i++;}return 0;
}

十二、TCP连接图

2MSL时长:保证最后一个ACK能成功被对端接收。
(等待期间,对端没收到我发的ACK,对端会再次发送FIN请求。)

十三、端口复用

当服务器先关闭,再关闭客户端,再启动服务器就会出错
因为服务器主动关闭要等2MSL,期间端口还被占用解决:在bind()之前插入以下代码//listenfd = socket(...);
int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (void*)&opt, sizeof(opt));

十四、半关闭状态

半关闭。
通信双方中,只有一端关闭通信。---FIN_WAIT_2
close(cfd);shutdown(int fd, int how);
how:SHUT_RD关读端SHUT_WR关写端SHUT_RDWR关读写shutdown()在关闭多个文件描述符应用的文件时,采用全关闭方法。close()只关闭一个。

十五、多路IO


十六、select函数

void FD_ZERO(fd_set *set);           ---清空一个文件描述符集合。fd_set rset;FD_ZERO(&rset);void FD_SET(int fd, fd_set *set);    ---将待监听的文件描述符,添加到监听集合中。FD_SET(3, &rset);FD_SET(5, &rset);FD_SET(6, &rset);void FD_CLR(int fd, fd_set *set);    ---将一个文件描述符从监听集合中移除。FD_CLR(4. &rset);int FD_ISSET(int fd, fd set *set);   ---判断一个文件描述符是否在监听集合中。返回值:在1,不在0FD_ISSET(4, &rset);int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);nfds:监听的所有文件描述符中,最大文件描述符+1readfds:读文件描述符监听集合。     传入、传出参数    writefds:写文件描述符监听集合。    传入、传出参数    NULLexceptfds:异常文件描述符监听集合   传入、传出参数    NULLtimeout:>0:设置监听超时时长。NULL:阻塞监听0:非阻塞监听,轮询返回值:>0:所有监听集合(3个)中,满足对应事件的总数。0:没有满足监听条件的文件描述符-1:errno

十七、select实现多路IO

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<arpa/inet.h>
#include<ctype.h>// 封装的错误处理函数
#include"wrap.h"#define SERV_PORT 6666int main(int argc, char* argv[]) {int listenfd, connfd;char buf[BUFSIZ];struct sockaddr_in clie_addr, serv_addr;socklen_t clie_addr_len;listenfd = Socket(AF_INET, SOCK_STREAM, 0);int opt = 1;setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));bzero(&serv_addr, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);serv_addr.sin_port = htons(SERV_PORT);Bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));Listen(listenfd, 128);fd_set rset, allset;		//定义读集合,备份集合allsetint ret, maxfd = 0, n, i, j;maxfd = listenfd;			//最大文件描述符FD_ZERO(&allset);			//清空监听集合FD_SET(listenfd, &allset);	//将待监听fd添加到监听集合中while (1) {rset = allset;		//备份ret = select(maxfd + 1, &rset, NULL, NULL, NULL);	//使用select监听if (ret < 0) {perr_exit("select error");}//listenfd满足监听的读事件if (FD_ISSET(listenfd, &rset)) {	clie_addr_len = sizeof(clie_addr);//建立链接,不会阻塞connfd = Accept(listenfd, (struct sockaddr*)&clie_addr, &clie_addr_len);FD_SET(connfd, &allset);	//将新产生的fd,添加到监听集合中,监听数据读事件if (maxfd < connfd) {maxfd = connfd;			//修改maxfd}if (ret == 1) {continue;	//说明select只返回一个,并且是listenfd,后续执行无须执行}}//处理满足读事件的fdfor (i = listenfd + 1; i <= maxfd; i++) {	//找到满足读事件的那个fdif (FD_ISSET(i, &rset)) {	n = Read(i, buf, sizeof(buf));//检测到客户端已经关闭链接if (n == 0) {			Close(i);FD_CLR(i, &allset);		//将关闭的fd,移除出监听集合}else if (n == -1) {perr_exit("read error");}for (j = 0; j < n; j++) {buf[j] = toupper(buf[j]);}write(i, buf, n);write(STDOUT_FILENO, buf, n);}}}Close(listenfd);return 0;
}

十八、select优缺点

缺点:监听上限受文件描述符限制。最大1024轮询fd效率太慢,一个个问,有的可能用不到也被询优点:跨平台。Win、Linux、MacOS、Unix解决:通过自定义数组放入需要被轮询的fd,避免不必要的轮询
// 用client数组解决
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<arpa/inet.h>
#include<ctype.h>#include"wrap.h"#define SERV_PORT 6666int main(int argc, char* argv[]) {int i, j, n, maxi;/*自定义数组client,防止遍历1024个文件描述符 FD_SETSIZE默认为1024*/int nready, client[FD_SETSIZE];int maxfd, listenfd, connfd, sockfd;/* #define INET_ADDRSTRLEN 16 */char buf[BUFSIZ], str[INET_ADDRSTRLEN];struct sockaddr_in clie_addr, serv_addr;socklen_t clie_addr_len;fd_set rset, allset;	/*set读事件文件描述符集合allset用来暂存*/listenfd = Socket(AF_INET, SOCK_STREAM, 0);int opt = 1;setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));bzero(&serv_addr, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);serv_addr.sin_port = htons(SERV_PORT);Bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));Listen(listenfd, 128);maxfd = listenfd;	/*起初listenfd即为最大文件描述符*/maxi = -1;			/*将来用作client[]的下标,初始值指向0个元素之前下标位置*/for (i = 0; i < FD_SETSIZE; i++) {client[i] = -1;				/*用-1初始化client[]*/}FD_ZERO(&allset);FD_SET(listenfd, &allset);		/*构造select监控文件描述符集*/while (1) {rset = allset;				/*每次循环时都重新设置select监控信号集*/nready = select(maxfd + 1, &rset, NULL, NULL, NULL);if (nready < 0) {perr_exit("select error");}/*说明有新的客户端连接请求*/if (FD_ISSET(listenfd, &rset)) {clie_addr_len = sizeof(clie_addr);//建立连接,不会阻塞connfd = Accept(listenfd, (struct sockaddr*)&clie_addr, &clie_addr_len);printf("received from %s at PORT %d\n",inet_ntop(AF_INET, &clie_addr.sin_addr, str, sizeof(str)),ntohs(clie_addr.sin_port));for (i = 0; i < FD_SETSIZE; i++) {/*找client[]中没有使用的位置*/if (client[i] < 0) {client[i] = connfd;		/*保存accept返回的文件描述符到client[]里*/break;}}/*达到select能监控的文件个数上限1024*/if (i == FD_SETSIZE) {fputs("too many clients\n", stderr);exit(1);}FD_SET(connfd, &allset);	/*向监控文件描述符集合allset添加新的文件描述符connfd*/if (connfd > maxfd) {maxfd = connfd;			/*select第一个参数需要*/}if (i > maxi) {maxi = i;				/*保证maxi存的总是client[]最后一个元素下标*/}if (--nready == 0) {continue;}}/*检测哪个clients有数据就绪*/for (i = 0; i <= maxi; i++) {if ((sockfd = client[i]) < 0) {continue;}if (FD_ISSET(sockfd, &rset)) {/*当client关闭连接时,服务器端也关闭对应连接*/if ((n = Read(sockfd, buf, sizeof(buf))) == 0) {Close(sockfd);FD_CLR(sockfd, &allset);	/*解除select对此文件描述符的监控*/client[i] = -1;}else if (n > 0) {for (j = 0; j < n; j++) {buf[j] = toupper(buf[j]);}Write(sockfd, buf, n);Write(STDOUT_FILENO, buf, n);if (--nready == 0) {break;}}}}}Close(listenfd);return 0;
}
http://www.dtcms.com/a/462420.html

相关文章:

  • 基于wordpress站点的域名迁移
  • spring 框架中常用注解汇总,及对应作用介绍、使用示例demo演示
  • 网站后台下载二级建造师报名官网
  • 济宁住房和城乡建设局网站秦皇岛 免费建网站
  • 企业网站建设报价方案动易官方网站
  • 制作一个 MBTI 人格测试网页项目
  • Echarts如何实现line的实线虚线的分段,并且虚实线连接点平滑过度效果(未来预测场景)?
  • 苍穹外卖day06
  • mysql大表批量查询中IN vs JOIN vs CTE 性能实验
  • Cryptomator:免费的加密工具,
  • 逐位加|二分
  • 外贸行业网站推广wordpress galleria
  • 没技术怎么做网站湛江的高铁站建在哪里
  • MySQL 中数据完整性约束、外键管理(含级联策略) 和多表查询
  • 做效果图的网站有哪些软件有哪些wordpress漂浮
  • 为什么ffmpeg进行视频合成有时长误差
  • 做旅游销售网站平台ppt百度的域名
  • 网站建营销型企业网站有哪些类型
  • 2008服务器网站专门做定制的网站
  • 【软件设计师中级】计算机组成与结构(四):总线系统 - 计算机的“高速公路网络“
  • 专注服务于站长和网站的信息平台.网站建设需要学ps吗
  • 视频直播点播平台EasyDSS推拉流技术结合无人机推流在道路交通巡检场景中的应用
  • 涂鸦T5AI开发板直播互动游戏控制器实现方案【全开源】
  • Spring Boot 应用启动机制详解
  • 河南省建设工程造价协会网站joomla 2.5:你的网站建设_使用与管理 pdf
  • 只有通过Motor 获取 mongodb的collection,才能正常使用 async with collection.watch()监听集合变更
  • 做一个网站 如何盈利网站开发一般用什么软件有哪些
  • 能够完美“适配”不同传感器的语音芯片WT2003H
  • 怎样建设网站的步骤网站建设中js控制什么
  • 陇南地网站建设黄骅市有什么好玩的地方