0

0

socket套接字详解(TCP与UDP)

little bottle

little bottle

发布时间:2019-04-10 15:24:12

|

3682人浏览过

|

来源于CSDN

转载

   学习linux,网络编程套接字是基础,也是新手学习的难点,通过本篇文章,读者可以通过图解、作者的代码实现思路全面理解ip地址、端口号、tcp、udp概念、socket api用法、模拟客户端/服务器通信等。

  • Mark:阅读blog + 代码实现耗时18分钟
  • 这里写图片描述


文章重点:
  • IP地址、端口号……

  • socket API

  • 实现UDP客户端/服务器


  • 套接字是网络编程中的一种通信机制,是支持TCP/IP的网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程。

前面介绍过,本地的进程间通信(IPC)有很多种方式,常见的总结以下几点:

 1、管道(包括无名管道和命名管道);
 2、消息队列;
 3、信号量;
 4、共享存储。
 5、……( Socket和Streams支持不同主机上的两个进程IPC)。
认识网络层通信过程:

这里写图片描述

初识IP:

(IP就是:Internet协议IP)

这里写图片描述

在通信时,IP有源IP和目的IP之分,对比寄快递:网络通信相当于收发快递,IP就是收件/发件人地址,仅仅知道地址还不行,还要知道派送人是谁?这就对比于网络中的端口号概念,端口号标识了一个进程,告诉操作系统,当前这个数据交给哪一个程序进行解析。

端口号:

端口号(port)是传输层协议的内容。

  • 端口号是一个2字节16位的整数;

  • 端口号用来标识一个进程,告诉操作系统,当前这个数据交给哪一个程序进行解析;

  • IP地址 + 端口号能标识网络上的某一台主机的某一个进程;

  • 一个端口号只能被一个进程占用。

端口号 & 进程:
  • 概念

进程有唯一的pid标识,端口号也能标识进程;

一个进程可以绑定多个端口号,一个端口号不能被多个进程绑定。

  • 源端口号 & 目的端口号

传输层协议(TCP/IP)的数据段中有两个端口号,分别叫做源端口号和目的端口号,就是在描述“数据是谁的?发给谁?”

TCP:

(TCP)传输控制协议,面向连接。是一种提供可靠数据传输的通用协议。

  • 传输层协议

    VIVA
    VIVA

    一个免费的AI创意视觉设计平台

    下载
  • 有连接

  • 可靠传输

  • 面向字节流

UDP:

(UDP)用户数据报协议,是一个面向无连接的协议。采用该协议不需要两个应用程序先建立连接。UDP协议不提供差错恢复,不能提供数据重传,因此该协议传输数据安全性差。

  • 传输层协议

  • 无连接

  • 不可靠传输

  • 面向数据报


网络字节序:

这里写图片描述

  • 如何定义网络数据流的地址?

这里写图片描述

其实很容易理解这个问题,就是C语言中比较讲究的大小端问题。

  • 发送机按内存地址从低到高顺序发送;

  • 接收主机按内存地址从低到高顺序保存;

  • TCP/IP规定:网络数据流应采用大端字节序,即地地址高字节

  • 不论主机是大端机还是小端机,都必须遵循TCP/IP规定;

  • 如果发送机是小端,就先将数据转成大端再发送。

socket API:
//创建socket文件描述符  (TCP/UDP,客户端+服务器)

int socket(int domain, int type, int protocol);

参数1(domain): 选择创建的套接字所用的协议族;
 AF_INET : IPv4协议;
 AF_INET6: IPv6协议;
 AF_LOCAL: Unix域协议;
 AF_ROUTE:路由套接口;
 AF_KEY :密钥套接口。
参数2(type):指定套接口类型,所选类型有:
 SOCK_STREAM:字节流套接字;
 SOCK_DGRAM : 数据报套接字;
 SOCK_RAW : 原始套接口。
 procotol: 使用的特定协议,一般使用默认协议(NULL)。

//绑定端口号  (TCP/IP,服务器)
int bind(int socket, const struct sockaddr *address, socklen_t address_len);

参数1(socket) : 是由socket()调用返回的并且未作连接的套接字描述符(套接字号)。
参数2(address):指向特定协议的地址指针。
参数3(address_len):上面地址结构的长度。
返回值:没有错误,bind()返回0,否则SOCKET_ERROR。

//开始监听socket  (TCP,服务器)
int listen(int socket, int backlog);

参数1(sockfd):是由socket()调用返回的并且未作连接的套接字描述符(套接字号)。
参数2(backlog):所监听的端口队列大小。

//接受请求  (TCP,服务器)
int accept(int socket, struct sockaddr* address, socklen_t* address_len);

参数1(socket) : 是由socket()调用返回的并且未作连接的套接字描述符(套接字号)。
参数2(address):指向特定协议的地址指针。
参数3(addrlen):上面地址结构的长度。
返回值:没有错误,bind()返回0,否则SOCKET_ERROR。

//建立连接  (TCP,客户端)
int connect(int sockfd, const struct struct sockaddr *addr, aocklen_t addrlen);
//关闭套接字
int close(int fd);

参数(fd):是由socket()调用返回的并且未作连接的套接字描述符(套接字号)。

socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4,IPv6,……

简单的TCP网络程序:
  • TCP客户—服务器程序的执行流程图:

这里写图片描述

服务器代码:

#include<iostream>
#include<unistd.h>
#include<stdio.h>
#include<string.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
using namespace std;

#define SERVER_PORT  5050               //端口号
#define SERVER_IP    "192.168.3.254"    //服务器ip
#define QUEUE_SIZE   5                  //所监听端口队列大小

int main(int argc, char *argv[])
{
    //创建一个套接字,并检测是否创建成功
    int sockSer;                        
    sockSer = socket(AF_INET, SOCK_STREAM, 0);
    if(sockSer == -1){
        perror("socket");
    }

    //设置端口可以重用,可以多个客户端连接同一个端口,并检测是否设置成功
    int yes = 1;
    if(setsockopt(sockSer, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1){
        perror("setsockopt");
    }

    struct sockaddr_in addrSer,addrCli;        //创建一个记录地址信息的结构体
    addrSer.sin_family = AF_INET;              //所使用AF_INET协议族
    addrSer.sin_port = htons(SERVER_PORT);     //设置地址结构体中的端口号
    addrSer.sin_addr.s_addr = inet_addr(SERVER_IP);   //设置其中的服务器ip

    //将套接字地址与所创建的套接字号联系起来。并检测是否绑定成功
    socklen_t addrlen = sizeof(struct sockaddr);
    int res = bind(sockSer,(struct sockaddr*)&addrSer, addrlen);
    if(res == -1)
        perror("bind");

    listen(sockSer, QUEUE_SIZE);       //监听端口队列是否由连接请求,如果有就将该端口设置位可连接状态,等待服务器接收连接

    printf("Server Wait Client Accept......\n");
    //如果监听到有连接请求接受连接请求。并检测是否连接成功,成功返回0,否则返回-1
    int sockConn = accept(sockSer, (struct sockaddr*)&addrCli, &addrlen);
    if(sockConn == -1)
        perror("accept");
    else
    {
        printf("Server Accept Client OK.\n");
        printf("Client IP:> %s\n", inet_ntoa(addrCli.sin_addr));
        printf("Client Port:> %d\n",ntohs(addrCli.sin_port));
    }

    char sendbuf[256];         //申请一个发送缓存区
    char recvbuf[256];         //申请一个接收缓存区
    while(1)
    {
        printf("Ser:>");
        scanf("%s",sendbuf);
        if(strncmp(sendbuf,"quit",4) == 0)    //如果所要发送的数据为"quit",则直接退出。
            break;
        send(sockConn, sendbuf, strlen(sendbuf)+1, 0);   //发送数据
        recv(sockConn, recvbuf, 256, 0);    //接收客户端发送的数据
        printf("Cli:> %s\n",recvbuf);
    }

    close(sockSer);         //关闭套接字
    return 0;
}

客户端代码:

#include<iostream>
#include<unistd.h>
#include<stdio.h>
#include<string.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
using namespace std;

#define SERVER_PORT  5050
#define SERVER_IP    "192.168.3.254"

int main(int argc, char *argv[])
{
    //创建客户端套接字号,并检测是否创建成功
    int sockCli;
    sockCli = socket(AF_INET, SOCK_STREAM, 0);
    if(sockCli == -1)
        perror("socket");

    //创建一个地址信息结构体,并对其内容进行设置
    struct sockaddr_in addrSer;     
    addrSer.sin_family = AF_INET;         //使用AF_INET协议族
    addrSer.sin_port = htons(SERVER_PORT);  //设置端口号
    addrSer.sin_addr.s_addr = inet_addr(SERVER_IP);   //设置服务器ip

    bind(sockCli,(struct sockaddr*)&addrCli, sizeof(struct sockaddr));    //将套接字地址与所创建的套接字号联系起来

    //创建一个与服务器的连接,并检测连接是否成功
    socklen_t addrlen = sizeof(struct sockaddr);
    int res = connect(sockCli,(struct sockaddr*)&addrSer, addrlen);
    if(res == -1)
        perror("connect");
    else
        printf("Client Connect Server OK.\n");

    char sendbuf[256];     //申请一个发送数据缓存区
    char recvbuf[256];     //申请一个接收数据缓存区
    while(1)
    {
        recv(sockCli, recvbuf, 256, 0);    //接收来自服务器的数据
        printf("Ser:> %s\n",recvbuf);
        printf("Cli:>");
        scanf("%s",sendbuf);
        if(strncmp(sendbuf,"quit", 4) == 0)    //如果客户端发送的数据为"quit",则退出。
            break;
        send(sockCli, sendbuf, strlen(sendbuf)+1, 0);   //发送数据
    }
    close(sockCli);       //关闭套接字
    return 0;
}
简单的UDP网络程序:

这里写图片描述

  • 相对与TCP来说,UDP安全性差,面向无链接。所以UDP地实现少了连接与接收连接的操作。所以在收发数据时就不能再用send()和recvfrom()了,而是用sendto()和recvto()之名从哪收发数据。
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);

参数1(sockfd):是由socket()调用返回的并且未作连接的套接字描述符(套接字号)
参数2(buf):指向存有发送数据的缓冲区的指针
参数3(len):缓冲区长度。
 **参数4(flags):**flags的值或为0,或为其他

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);

参数1(sockfd):是由socket()调用返回的并且未作连接的套接字描述符(套接字号)
参数2(buf):指向存有接收数据的缓冲区的指针
参数3(len):缓冲区长度
 **参数4(flags):**flags的值或为0,或为其他

服务器端代码:

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/socket.h>

int main()
{
    //创建一个套接字,并检测是否创建成功
    int sockSer = socket(AF_INET, SOCK_DGRAM, 0);
    if(sockSer == -1)
        perror("socket");

    struct sockaddr_in addrSer;  //创建一个记录地址信息的结构体 
    addrSer.sin_family = AF_INET;    //使用AF_INET协议族 
    addrSer.sin_port = htons(5050);     //设置地址结构体中的端口号
    addrSer.sin_addr.s_addr = inet_addr("192.168.3.169");  //设置通信ip

    //将套接字地址与所创建的套接字号联系起来,并检测是否绑定成功
    socklen_t addrlen = sizeof(struct sockaddr);
    int res = bind(sockSer,(struct sockaddr*)&addrSer, addrlen);
    if(res == -1)
        perror("bind");

    char sendbuf[256];    //申请一个发送数据缓存区
    char recvbuf[256];    //申请一个接收数据缓存区
    struct sockaddr_in addrCli;
    while(1)
    {
        recvfrom(sockSer,recvbuf,256,0,(struct  sockaddr*)&addrCli, &addrlen);     //从指定地址接收客户端数据
        printf("Cli:>%s\n",recvbuf);

        printf("Ser:>");    
        scanf("%s",sendbuf);
        sendto(sockSer,sendbuf,strlen(sendbuf)+1,0,(struct sockaddr*)&addrCli, addrlen);    //向客户端发送数据
    }
    return 0;
}

客户端代码:

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/socket.h>

int main()
{
    //创建一个套接字,并检测是否创建成功
    int sockCli = socket(AF_INET, SOCK_DGRAM, 0);
    if(sockCli == -1){
        perror("socket");
    }

    addrSer.sin_family = AF_INET;    //使用AF_INET协议族 
    addrSer.sin_port = htons(5050);     //设置地址结构体中的端口号
    addrSer.sin_addr.s_addr = inet_addr("192.168.3.169");  //设置通信ip
    socklen_t addrlen = sizeof(struct sockaddr);


    char sendbuf[256];    //申请一个发送数据缓存区
    char recvbuf[256];    //申请一个接收数据缓存区

    while(1){
        //向客户端发送数据
        printf("Cli:>");
        scanf("%s",sendbuf);
        sendto(sockCli, sendbuf, strlen(sendbuf)+1, 0, (struct sockaddr*)&addrSer, addrlen);   
        接收来自客户端的数据
        recvfrom(sockCli, recvbuf, BUFFER_SIZE, 0, (struct sockaddr*)&addrSer, &addrlen);
        printf("Ser:>%s\n", recvbuf);

    }

    return 0;
}

这里写图片描述

<br/>
【推荐课程:TCP/IP视频教程

作者最新文章

jq是指什么?

2019-05-31 16:04

vue是做什么的

2019-05-31 16:58

mysql能干什么

2019-05-31 17:15

怎么用cmd进入mysql

2019-05-31 17:24

mysql怎么输入

2019-05-31 17:41

mysql免费版好用么

2019-05-31 17:53

mysql慢查询是什么

2019-05-31 18:00

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
C# ASP.NET Core微服务架构与API网关实践
C# ASP.NET Core微服务架构与API网关实践

本专题围绕 C# 在现代后端架构中的微服务实践展开,系统讲解基于 ASP.NET Core 构建可扩展服务体系的核心方法。内容涵盖服务拆分策略、RESTful API 设计、服务间通信、API 网关统一入口管理以及服务治理机制。通过真实项目案例,帮助开发者掌握构建高可用微服务系统的关键技术,提高系统的可扩展性与维护效率。

16

2026.03.11

Go高并发任务调度与Goroutine池化实践
Go高并发任务调度与Goroutine池化实践

本专题围绕 Go 语言在高并发任务处理场景中的实践展开,系统讲解 Goroutine 调度模型、Channel 通信机制以及并发控制策略。内容包括任务队列设计、Goroutine 池化管理、资源限制控制以及并发任务的性能优化方法。通过实际案例演示,帮助开发者构建稳定高效的 Go 并发任务处理系统,提高系统在高负载环境下的处理能力与稳定性。

23

2026.03.10

Kotlin Android模块化架构与组件化开发实践
Kotlin Android模块化架构与组件化开发实践

本专题围绕 Kotlin 在 Android 应用开发中的架构实践展开,重点讲解模块化设计与组件化开发的实现思路。内容包括项目模块拆分策略、公共组件封装、依赖管理优化、路由通信机制以及大型项目的工程化管理方法。通过真实项目案例分析,帮助开发者构建结构清晰、易扩展且维护成本低的 Android 应用架构体系,提升团队协作效率与项目迭代速度。

75

2026.03.09

JavaScript浏览器渲染机制与前端性能优化实践
JavaScript浏览器渲染机制与前端性能优化实践

本专题围绕 JavaScript 在浏览器中的执行与渲染机制展开,系统讲解 DOM 构建、CSSOM 解析、重排与重绘原理,以及关键渲染路径优化方法。内容涵盖事件循环机制、异步任务调度、资源加载优化、代码拆分与懒加载等性能优化策略。通过真实前端项目案例,帮助开发者理解浏览器底层工作原理,并掌握提升网页加载速度与交互体验的实用技巧。

95

2026.03.06

Rust内存安全机制与所有权模型深度实践
Rust内存安全机制与所有权模型深度实践

本专题围绕 Rust 语言核心特性展开,深入讲解所有权机制、借用规则、生命周期管理以及智能指针等关键概念。通过系统级开发案例,分析内存安全保障原理与零成本抽象优势,并结合并发场景讲解 Send 与 Sync 特性实现机制。帮助开发者真正理解 Rust 的设计哲学,掌握在高性能与安全性并重场景中的工程实践能力。

218

2026.03.05

PHP高性能API设计与Laravel服务架构实践
PHP高性能API设计与Laravel服务架构实践

本专题围绕 PHP 在现代 Web 后端开发中的高性能实践展开,重点讲解基于 Laravel 框架构建可扩展 API 服务的核心方法。内容涵盖路由与中间件机制、服务容器与依赖注入、接口版本管理、缓存策略设计以及队列异步处理方案。同时结合高并发场景,深入分析性能瓶颈定位与优化思路,帮助开发者构建稳定、高效、易维护的 PHP 后端服务体系。

420

2026.03.04

AI安装教程大全
AI安装教程大全

2026最全AI工具安装教程专题:包含各版本AI绘图、AI视频、智能办公软件的本地化部署手册。全篇零基础友好,附带最新模型下载地址、一键安装脚本及常见报错修复方案。每日更新,收藏这一篇就够了,让AI安装不再报错!

168

2026.03.04

Swift iOS架构设计与MVVM模式实战
Swift iOS架构设计与MVVM模式实战

本专题聚焦 Swift 在 iOS 应用架构设计中的实践,系统讲解 MVVM 模式的核心思想、数据绑定机制、模块拆分策略以及组件化开发方法。内容涵盖网络层封装、状态管理、依赖注入与性能优化技巧。通过完整项目案例,帮助开发者构建结构清晰、可维护性强的 iOS 应用架构体系。

222

2026.03.03

C++高性能网络编程与Reactor模型实践
C++高性能网络编程与Reactor模型实践

本专题围绕 C++ 在高性能网络服务开发中的应用展开,深入讲解 Socket 编程、多路复用机制、Reactor 模型设计原理以及线程池协作策略。内容涵盖 epoll 实现机制、内存管理优化、连接管理策略与高并发场景下的性能调优方法。通过构建高并发网络服务器实战案例,帮助开发者掌握 C++ 在底层系统与网络通信领域的核心技术。

33

2026.03.03

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
进程与SOCKET
进程与SOCKET

共6课时 | 0.4万人学习

swoole入门物联网开发与实战
swoole入门物联网开发与实战

共15课时 | 1.3万人学习

swoole项目实战(第二季)
swoole项目实战(第二季)

共15课时 | 1.3万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号