Linux网络编程---Socket编程

一、网络套接字

一个文件描述符指向一个套接字(该套接字内部由内核借助两个缓冲区实现。)

在通信过程中,套接字一定是成对出现的

套接字通讯原理示意图:

二、预备知识 

1.  网络字节序

内存中的多字节数据相对于内存地址有大端和小端之分

小端法:(PC本地存储)高位存高地址,低位存低地址

大端法:(网络存储)高位存低地址,低位存高地址

网络的数据流采用大端字节序,而本地的数据流采用小端字节序,因此要通过函数来完成络字节序和主机字节序的转换

htonl:本地 -->网络 (IP协议)        本地字节序转网络字节序,转32位

htons:本地 --> 网络 (port端口)

ntohl:网络 --> 本地 (IP)

ntohs:网络 --> 本地 (port)

其中:h表示host,n表示network,l表示32位(4字节)长整数,s表示16位短整数

2. IP地址转换函数 

int inet_pton(int af, const char *src, void *dst);

本地字节序(string IP)转换成网络字节序

参数

        aft:指定 IP 地址的类型,通常是AF_INET或AF_INET6
        src:传入,IP地址(点分十进制)
        dst:传出,转换后的网络字节序的IP地址。
返回值
        成功:1
        异常:0,说明src指向的不是一个有效的ip地址。

        失败:-1

const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);

网络字节序转换成本地字节序(string IP)

参数

        aft:AF_INET、AF_INET6
        src:网络字节序IP地址
        dst:本地字节序(string IP)

        size:dst的大小
返回值
        成功:dst

        失败:NULL

3. sockaddr地址结构

struct sockaddr_in addr ;
addr.sin_family = AF_INET/AF_INET6                man 7 ip
addr.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);

  1. sin_family:表示你要使用的地址结构类型,AF_INET是IPV4,AF_INET6是IPV6;
  2. sin_port:网络字节序的端口号,因此要使用htons转换一下;
  3. struct in_addr sin_addr:一个结构体,里面有一个s_addr,要传入网络字节序的ip地址,因此要使用inet_pton函数;也可以使用第二种方法使用宏:addr.sin_addr. s_addr = htonl(INADDR_ANY);

三、网络套接字函数 

1. socket模型创建流程分析

当服务器端调用socket函数时会产生一个套接字,而accept函数会阻塞监听客户端连接,当有客户端过来连接的时候,该函数会返回一个新的套接字去和客户端连接,因此一个socket建立会有两个套接字,另外一个套接字用于监听。即:整个流程一共用到3个套接字,一对用于通信,一个用于监听

2. socket函数

头文件

#include <sys/types.h>

#include <sys/socket.h>


int socket(int domain, int type,int protocol) ;        创建一个套接字

参数:
        domain:AF_INET、AF_INET6、AF_UNIX
        type:数据传输协议,SOCK_STREAM或SOCK_DGRAM
        protocol:默认传0,根据type来选择,SOCK_STREAM的代表协议是TCP,SOCK_DGRAM的是UDP

返回值:

        成功:新套接字所对应文件描述符
        失败::-1 errno

3. bind函数 

头文件:

#include <arpa/inet.h>

int bind(int sockfd,const struct sockaddr *addr,socklen_t addrlen) ;
给socket绑定一个地址结构(IP+port)

参数:
        sockfd:socket函数的返回值
                struct sockaddr_in addr;

                addr.sin_family= AF_INET;

                addr.sin_port = htons(8888);
                addr.sin_addr. s_addr = htonl (INADDR_ANY);

        网络地址为INADDR_ANY,这个宏表示本地的任意IP地址,因为服务器可能有多个网卡,每个网卡也可能绑定多个IP地址,这样设置可以在所有的IP地址上监听,直到与某个客户端建立了连接时才确定下来到底用哪个IP地址
        addr:传入参数(struct sockaddr *)&addr
        addrlen:sizeof(addr)地址结构的大小。
返回值:
        成功:0
        失败:-1 errno

4. listen函数

int listen(int sockfd, int backlog); 

设置同时与服务器建立连接的上限数(同时进行3次握手的客户端数量) 

参数:   

        sockfd:socket的返回值

        backlog:上限数值

返回值

        成功:0

        失败:-1 error

5. accept函数

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); 

阻塞等待客户端建立连接,成功的话 返回一个与客户端成功连接的socket文件描述符。

参数:

        sockfd:socket的返回值

        addr:传出参数,成功与服务器建立连接的那个客户端的地址结构(IP+port)

        addrlen: 传入传出参数,&clit_addr_len

                        入:addr的大小

                        出:客户端addr的实际大小

                socklen_t clit_addr_len = sizeof(addr);

返回值

        成功:能与服务器进行数据通信的 socket 对应的文件描述符

        失败:-1 error

6. connect函数

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); 

使用客户端现有的socket与服务器进行连接。

参数:

        sockfd:socket的返回值

                      struct sockaddr_in serv_addr;        //服务器地址结构
                      serv_addr.sin_family = AF_INET;
                      serv_addr.sin_port = htons(SERV_PORT);//和服务器bind时设定的port一致
                      inet_pton(AF_INET,"服务器的IP地址",&serv_addr.sin_addr.s_addr);

        addr:传入参数,服务器的地址结构

                inet_pton()

        addrlen:服务器地址结构的大小

返回值

        成功:0

        失败:-1 error

如果不使用bind绑定客户端地址结构,采用"隐式绑定" 

四、CS模型的TCP通信流程分析

server:

1. socket()                创建socket
2. bind()                    绑定服务器地址结构
3. listen()                  设置监听上限
4. accep()                 阻塞监听客户端连接
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()

 server的实现,server.c的作用是从客户端读字符,然后将每个字符转换为大写并回送给客户端

#include<stdio.h>
#include<stdlib.h>
#include<arpa/inet.h>
#include<ctype.h>
#include<sys/socket.h>
#include<unistd.h>
#include<string.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;char buf[BUFSIZ];//4096struct 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 errno");}ret = bind(lfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr));if (ret == -1){sys_err("bind error");}ret = listen(lfd,128);if (ret == -1){sys_err("listen error");}clit_addr_len = sizeof(clit_addr);cfd = accept(lfd,(struct sockaddr *)&clit_addr,&clit_addr_len);if (cfd == -1){sys_err("accept error");}while(1){ret = read(cfd,buf,sizeof(buf));write(STDOUT_FILENO,buf,ret);for (int i = 0;i<ret;i++){buf[i] = toupper(buf[i]);}write(cfd,buf,ret);}close(lfd);close(cfd);return 0;
}

可以通过命令nc+ip地址+端口号进行连接,测试服务器,可以看到能够正常的小写转大写,输出如下所示:

client的实现,client.c的作用是从命令行参数中获得一个字符串发给服务器,然后接收服务器返回的字符串并打印

#include<stdio.h>
#include<stdlib.h>
#include<arpa/inet.h>
#include<ctype.h>
#include<sys/socket.h>
#include<unistd.h>
#include<string.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 counter = 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,"192.168.88.129",&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 == -1){sys_err("connect error");}while(--counter){write(cfd,"hello\n",6);ret = read(cfd,buf,sizeof(buf));write(STDOUT_FILENO,buf,ret);sleep(1);}close(cfd);return 0;
}

在server.c中加入以下代码打印ip及端口号:

printf("client ip is:%s port:%d\n",inet_ntop(AF_INET,&clit_addr.sin_addr.s_addr,client_IP,sizeof(client_IP)),ntohs(clit_addr.sin_port));

两个终端分别执行服务端和客户端,输出如下所示: 

五、错误函数封装 

        上面的例子不仅功能简单,而且简单到几乎没有什么错误处理,我们知道,系统调用不能保证每次都成功,必须进行出错处理,这样一方面可以保证程序逻辑正常,另一方面可以迅速得到故障信息。

        为使错误处理的代码不影响主程序的可读性,我们把与socket相关的一些系统函数加上错误处理代码包装成新的函数,做成一个模块wrap.c:

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>void perr_exit(const char *s)
{perror(s);exit(-1);
}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;
}int Bind(int fd, const struct sockaddr *sa, socklen_t salen)
{int n;if ((n = bind(fd, sa, salen)) < 0)perr_exit("bind error");return n;
}int Connect(int fd, const struct sockaddr *sa, socklen_t salen)
{int n;if ((n = connect(fd, sa, salen)) < 0)perr_exit("connect error");return n;
}int Listen(int fd, int backlog)
{int n;if ((n = listen(fd, backlog)) < 0)perr_exit("listen error");return n;
}int Socket(int family, int type, int protocol)
{int n;if ((n = socket(family, type, protocol)) < 0)perr_exit("socket error");return n;
}ssize_t Read(int fd, void *ptr, size_t nbytes)
{ssize_t n;again:if ( (n = read(fd, ptr, nbytes)) == -1) {if (errno == EINTR)goto again;elsereturn -1;}return n;
}ssize_t Write(int fd, const void *ptr, size_t nbytes)
{ssize_t n;again:if ( (n = write(fd, ptr, nbytes)) == -1) {if (errno == EINTR)goto again;elsereturn -1;}return n;
}int Close(int fd)
{int n;if ((n = close(fd)) == -1)perr_exit("close error");return n;
}/*参三: 应该读取的字节数*/
ssize_t Readn(int fd, void *vptr, size_t n)
{size_t  nleft;              //usigned int 剩余未读取的字节数ssize_t nread;              //int 实际读到的字节数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;
}

wrap.h

#ifndef __WRAP_H_
#define __WRAP_H_void perr_exit(const char *s);
int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr);
int Bind(int fd, const struct sockaddr *sa, socklen_t salen);
int Connect(int fd, const struct sockaddr *sa, socklen_t salen);
int Listen(int fd, int backlog);
int Socket(int family, int type, int protocol);
ssize_t Read(int fd, void *ptr, size_t nbytes);
ssize_t Write(int fd, const void *ptr, size_t nbytes);
int Close(int fd);
ssize_t Readn(int fd, void *vptr, size_t n);
ssize_t Writen(int fd, const void *vptr, size_t n);
ssize_t my_read(int fd, char *ptr);
ssize_t Readline(int fd, void *vptr, size_t maxlen);#endif

错误函数封装在多线程并发服务器实现中使用

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/4529.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

同事上班这样摸鱼,我坐边上咋看他都在专心写代码啊

我边上有个同事&#xff0c;我坐他边上&#xff0c;但是每天看着他都眉头紧锁&#xff0c;忙的不亦乐乎&#xff0c;但终于有一天&#xff0c;我发现了他上班摸鱼的秘诀。 我劝你千万不要学会这4招&#xff0c;要不就该不好好上班了。 目录 1 上班看电影&#xff1f; 2 上班…

自建SQL server 服务无法启动,提示评估期已过

问题背景 在服务器内安装的SQL server无法启动&#xff0c;报错提示如下截图&#xff0c;提示错误代码17051&#xff1a; 结合系统日志查看应用程序日志详情提示评估期已过&#xff0c;报错如下 问题原因 出现此报错原因是SQL server 安装时&#xff0c;使用的评估版本&#xf…

网络安全的防护措施有哪些?

1. 安全策略和合规性 2. 物理和网络安全 3. 数据加密 4. 软件和系统更新 5. 访问控制 6. 威胁监测和响应 7. 员工培训和安全意识 8. 备份和灾难恢复 零基础入门学习路线 视频配套资料&国内外网安书籍、文档 网络安全面试题 网络安全的防护措施多种多样&#xff0c…

基于Spring Boot的商务安全邮件收发系统设计与实现

基于Spring Boot的商务安全邮件收发系统设计与实现 开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/idea 系统部分展示 已发送效果图&#xff0c;用户可以对已发送信息…

【GitHub】2FA认证(双重身份验证)

GitHub 2FA认证&#xff08;双重身份验证&#xff09; 写在最前面一、使用 TOTP 应用程序配置双2FA&#xff08;双因素身份验证&#xff09;1. 介绍2. github3. 认证 官网介绍小结 & 补充 &#xff1a;权限不足or验证码错误问题 &#x1f308;你好呀&#xff01;我是 是Yu欸…

张大哥笔记:普通人可以靠知识付费赚到钱吗?

大家好&#xff0c;我是张大哥&#xff0c;今天给大家聊聊普通人怎么做知识付费赚钱这个话题&#xff0c;首先科普一下&#xff0c;什么是知识付费&#xff1f;把知识变成产品或服务&#xff0c;以实现商业价值的行为就是知识付费&#xff01; 做知识付费类的项目&#xff0c;首…

MySQL数据库基础(数据库的基本操作、常用的数据类型、表的相关操作)

前言 今天我们将介绍数据库的基本操作、常用的数据类型、表的相关操作 一、数据库的基本操作 1.1 显示当前的数据库 操作代码 show databases;1.2 创建数据库 基本语法&#xff1a; 1. //创建数据库 create database examble;2. create database if not exists exist exa…

CentOS命令大全:掌握关键命令及其精妙用法!

CentOS是一种流行的开源企业级Linux发行版&#xff0c;它基于Red Hat Enterprise Linux (RHEL)的源代码构建。对于系统管理员和运维工程师来说&#xff0c;掌握CentOS的常用命令至关重要。 这些命令不仅可以帮助管理服务器&#xff0c;还可以进行故障排查、性能监控和安全加固等…

代码随想录(番外)图论3|1020. 飞地的数量|130. 被围绕的区域

代码随想录&#xff08;番外&#xff09;图论3|1020. 飞地的数量|130. 被围绕的区域 1020. 飞地的数量 class Solution { public:int dir[4][2]{0,1,1,0,0,-1,-1,0};int count;void dfs(vector<vector<int>>& grid,int x,int y){grid[x][y]0;count;for(int i…

人形机器人核心架构梳理

定义&#xff1a;机器人是能进行运动、操纵或定位且具有一定程度自主能力的可编程执行机构。按外在形态分类可分为传统机器人和人形机器人&#xff0c;其中人形机器人是一种利用人工智能和机器人技术制造的具有类似人类外观和行为的机器人。 人形机器人发展历程&#xff1a; 人…

C++之运算符重载

一&#xff1a;运算符重载 C为了增强代码的可读性引入了运算符重载&#xff0c;运算符重载是具有特殊函数名的函数&#xff0c;也具有其 返回值类型&#xff0c;函数名字以及参数列表&#xff0c;其返回值类型与参数列表与普通的函数类似。 函数名字为&#xff1a;关键字oper…

Linux基础——冯诺依曼体系结构与操作系统

前言&#xff1a;在进入Linux进阶知识之前&#xff0c;我们还需理解最后一点知识&#xff0c;先认识理解冯诺依曼体系结构&#xff0c;再认识理解操作系统定位这样才能更好的理解后面的知识 本篇主要内容&#xff1a; 冯诺依曼体系结构操作系统概念与定位 冯诺依曼系统 1. 冯诺…

Flink学习(九)-jar 包提交给 flink 集群执行

一、界面执行 1&#xff0c;点击左侧的 submit new job&#xff0c;然后点击add New 2&#xff0c;粘贴程序入口&#xff0c;设置并行度 3&#xff0c;执行后&#xff0c;就可以在 taskManager 中找到相关任务了 二、控制台执行 在命令行中&#xff0c;在flink 的安装目录下&…

gitee关联picgo设置自己的typora_图床

一&#xff1a;去gitee官网创建仓库&#xff1a;typora_图床 1.百度搜索关键字&#xff1a;gitee&#xff0c;进入官网 2.进入gitee登录或者注册自己的账号 3.进入主页后&#xff0c;点击右上方 4.点击新建仓库 5.设置仓库名&#xff1a;typora_图床 6.点击5的创建&#xff0…

云渲染一张图多少钱

使用云渲染渲染一张效果图的价格没法确定多少钱一张&#xff0c;云渲染一张图的价格会受到多个因素的影响&#xff0c;如云渲染平台的定价策略、所选的渲染配置、优惠政策以及你提交的场景任务等。因此&#xff0c;无法给出确切的单一价格。 不同的云渲染平台会有不同的定价模…

前端计算机网络之网络模型

什么是网络模型 对于前端开发者而言&#xff0c;理解网络模型的概念是非常重要的。网络模型是描述数据如何在网络中传输和处理的框架和规则&#xff0c;它有助于前端开发者更好地理解和优化应用程序与服务器之间的通信过程。 常用的两类模型 前端开发者需要了解的网络模型主…

IDEA插件

POJO to JSON —— 实体转Json Smart Input —— 自动切换中英文 Translation —— 翻译 Maven Helper —— 依赖管理 .ignore —— 忽略提交文件 enum-quick-generate —— 枚举代码生成 粘贴到项目包下

Qt | 标准、复选、单选、工具、命令按钮大全

01、QPushButton QPushButton 类(标准按钮) 示例 3:默认按钮与自动默认按钮 02、QCheckBox QCheckBox 类(复选按钮) 1、复选按钮的第三状态(见右图 Qt5.10.1 的选中状态):是指除了选中 和未选中状态之外的第三种状态,这种状态用来指示“不变”,表 示用户既不选中也不取…

数据库常考理论

1 笛卡尔积X与自然连接∞的区别 2 求候选键 3 无损连接及函数依赖 4 范式判断 笛卡尔积&#xff1a;S1*S2,产生的结果包括S1和S2的所有属性列&#xff0c;并且S1中每条记录依次和S2中所有记录 组合成一条记录&#xff0c;最终属性列为S1S2属性列&#xff0c;记录数为S1*S2记…

新闻 | 电子系协同智能中心与昌平区未来高教园及多所高校开展交流,共话智能无人平台建设

2024年4月8日&#xff0c;清华大学电子工程系在北京昌平两岸共盈科技产业园电子系地空协同智能无人平台基地成功举办“美团杯”智能无人机挑战赛&#xff0c;清华大学电子系党委书记沈渊、昌平区未来城管委会校城融合处处长熊玉川、清华大学团委副书记黄峰等出席。此外来自昌平…