Linux网络编程---Socket编程

news2024/12/30 0:48:02

一、网络套接字

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

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

套接字通讯原理示意图:

二、预备知识 

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: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 9527

void 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];//4096

        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 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 9527

void 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;
                else
                        perr_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;
                else
                        return -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;
                else
                        return -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;
                        else
                                return -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;
                        else
                                return -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;
                } else
                        return -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.coloradmin.cn/o/1621943.html

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

相关文章

Ubuntu终端常用指令

cat cat 读取文件的内容 1、ls 一、 1、ll 显示当前目录下文件的详细信息,包括读写权限,文件大小,文件生成日期等(若想按照更改的时间先后排序,则需加-t参数,按时间降序(最新修改的时间排在最前)执行: $ ll -t, 按时间升序执行: $ ll -t | tac): ll 2、查看当前所处路径(完整…

Qt中常用对话框

Qt中的对话框&#xff08;QDialog&#xff09;是用户交互的重要组件&#xff0c;用于向用户提供特定的信息、请求输入、或进行决策。Qt提供了多种标准对话框以及用于自定义对话框的类。以下将详细介绍几种常用对话框的基本使用、使用技巧以及注意事项&#xff0c;并附带C示例代…

node.js 解析post请求 方法一

前提&#xff1a;依旧以前面发的node.js服务器动态资源处理代码 具体见 http://t.csdnimg.cn/TSNW9为模板&#xff0c;在这基础上进行修改。与动态资源处理代码不同的是&#xff0c;这次的用户信息我们借用表单来实现。post请求解析来获取和展示用户表单填写信息 1》代码难点&…

全彩屏负氧离子监测站的使用

TH-FZ5在繁忙的都市生活中&#xff0c;我们往往忽视了一个至关重要的问题——空气质量。随着工业化的进程加速&#xff0c;空气污染已成为影响人们健康的一大隐患。为了实时监测和了解身边的空气质量&#xff0c;全彩屏负氧离子监测站应运而生&#xff0c;成为了我们守护呼吸健…

企业集成平台建设方案(技术方案+功能设计)

企业集成平台建设方案及重点难点攻坚 基础支撑平台主要承担系统总体架构与各个应用子系统的交互&#xff0c;第三方系统与总体架构的交互。需要满足内部业务在该平台的基础上&#xff0c;实现平台对于子系统的可扩展性。基于以上分析对基础支撑平台&#xff0c;提出了以下要求&…

稀碎从零算法笔记Day59-LeetCode: 感染二叉树需要的总时间

题型&#xff1a;树、图、BFS、DFS 链接&#xff1a;2385. 感染二叉树需要的总时间 - 力扣&#xff08;LeetCode&#xff09; 来源&#xff1a;LeetCode 题目描述 给你一棵二叉树的根节点 root &#xff0c;二叉树中节点的值 互不相同 。另给你一个整数 start 。在第 0 分钟…

25计算机考研院校数据分析 | 北京航空航天大学

北京航空航天大学(Beihang University)&#xff0c;简称北航&#xff0c;由中华人民共和国工业和信息化部直属&#xff0c;中央直管副部级建制&#xff0c;位列“双一流”、"211工程”、"985工程”&#xff0c;入选“珠峰计划”、"2011计划”、“111计划”、&qu…

STM32标准库ADC和DMA知识点总结

目录 前言 一、ADC模数转换器 &#xff08;1&#xff09;AD单通道 &#xff08;2&#xff09;AD多通道 二、DMA原理和应用 &#xff08;1&#xff09;DMA数据转运&#xff08;内存到内存&#xff09; &#xff08;2&#xff09;DMAAD多同道&#xff08;外设到内存&#x…

debian和ubuntu的核心系统和系统命令的区别

Debian和Ubuntu虽然有很深的渊源&#xff0c;都是基于Debian的发行版&#xff0c;但它们在核心系统和系统命令上还是有一些差别的。以下是一些主要的不同之处&#xff1a; 1. 发布周期&#xff1a; - Debian&#xff1a; Debian项目采用滚动发布模型&#xff0c;持续更新&a…

SpringCloud Alibaba--nacos配置中心

目录 一.基础介绍 1.1概念 1.2 功能 二.实现 2.1 依赖 2.2 新建配置文件 2.3 克隆 2.4 配置bootstap.yml文件 三.测试 一.基础介绍 1.1概念 在微服务架构中&#xff0c;配置中心就是统一管理各个微服务配置文件的服务。把传统的单体jar包拆分成多个微服务后&#xf…

到底什么是认证

认证和授权 什么是认证 认证 (Authentication) 是根据凭据验明访问者身份的流程。即验证“你是你所说的那个人”的过程。 身份认证&#xff0c;通常通过用户名/邮箱/手机号以及密码匹配来完成&#xff0c;也可以通过手机/邮箱验证码或者生物特征&#xff08;如&#xff1a;指纹…

LangChain的核心模块和实战

主要模型 LLM:对话模型, 输入和输出都是文本Chat Model: 输入输出都是数据结构 模型IO设计 Format: 将提示词模版格式化Predict: langchain就是通过predict的方式调用不同的模型, 两个模型的区别不大, Chat Model 是以LLM为基础的.Parese: langchain还可以对结果进行干预, 得…

css盒子设置圆角边框的方法

前言 欢迎来到我的博客 个人主页&#xff1a;北岭敲键盘的荒漠猫-CSDN博客 本文为我整理的设置圆角边框的方法 需求描述 我们在设置盒子边框时&#xff0c;他总是方方正正的。 我们想让这个直直的边框委婉一点该怎么办呢。这个就提到了我们这篇文章讲的东西&#xff1a; bord…

二分查找知识点及练习题

知识点讲解 一、没有相同元素查找 请在一个有序递增数组中&#xff08;不存在相同元素&#xff09;&#xff0c;采用二分查找&#xff0c;找出值x的位置&#xff0c;如果x在数组中不存在&#xff0c;请输出-1&#xff01; 输入格式 第一行&#xff0c;一个整数n&#xff0c;代…

DevOps(十二)Jenkins实战之Web发布到远程服务器

前面两篇博文介绍了怎么将django开发的web应用推送到gitlab源码仓库&#xff0c;然后jenkins服务器从gitlab仓库拉下来&#xff0c;布署到jenkins服务器上&#xff0c;并用supervisor进行进程管理&#xff0c;保证web应用一直能正常运行&#xff0c;今天我们继续优化&#xff0…

政安晨:【深度学习神经网络基础】(十三)—— 卷积神经网络

目录 概述 LeNet-5 卷积层 最大池层 稠密层 针对MNIST数据集的卷积神经网络 总之 政安晨的个人主页&#xff1a;政安晨 欢迎 &#x1f44d;点赞✍评论⭐收藏 收录专栏: 政安晨的机器学习笔记 希望政安晨的博客能够对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎…

绿色便携方式安装apache+mysql+tomcat+php集成环境并提供控制面板

绿色便携方式安装带控制面板的ApacheMariaDBTomcatPHP集成环境 目录 绿色便携方式安装带控制面板的ApacheMariaDBTomcatPHP集成环境[TOC](目录) 前言一、XAMPP二、安装和使用1.安装2.使用 三、可能的错误1、检查端口占用2、修改端口 前言 安装集成环境往往配置复杂&#xff0c…

Gradio 最快创建Web 界面部署到服务器并演示机器学习模型,本文提供教学案例以及部署方法,避免使用繁琐的django

最近学习hugging face里面的物体检测模型&#xff0c;发现一个方便快捷的工具&#xff01; Gradio 是通过友好的 Web 界面演示机器学习模型的最快方式&#xff0c;以便任何人都可以在任何地方使用它&#xff01; 一、核心优势&#xff1a; 使用这个开发这种演示机器学习模型的…

leetcode-比较版本号-88

题目要求 思路 1.因为字符串比较大小不方便&#xff0c;并且因为需要去掉前导的0&#xff0c;这个0我们并不知道有几个&#xff0c;将字符串转换为数字刚好能避免。 2.当判断到符号位的时候加加&#xff0c;跳过符号位。 3.判断数字大小&#xff0c;来决定版本号大小 4.核心代…

LabVIEW和MES系统的智能化车间数据对接

LabVIEW和MES系统的智能化车间数据对接 随着工业4.0时代的到来&#xff0c;智能制造成为推动制造业高质量发展的重要手段。其中&#xff0c;数字化车间作为智能制造的重要组成部分&#xff0c;其设计与实现至关重要。在数字化车间环境下&#xff0c;如何利用LabVIEW软件与MES系…