Linux网络编程:socket、客户端服务器端使用socket通信(TCP)

news/2024/5/17 19:25:19 标签: linux网络编程, socket, 套接字, TCP

socket

socket套接字),用于网络中不同主机间进程的通信。

socket是一个伪文件,包含读缓冲区、写缓冲区。

socket必须成对出现。

socket可以建立主机进程间的通信,但需要协议(IPV4、IPV6等)、port端口、IP地址。


客户端服务端socket网络通信步骤(TCP

服务器端:

        (1)创建流式socket套接字

                a)此socket套接字一直用于后续的监听连接。

                b)socket函数。

        (2)绑定本机IP地址和port。

                b)bind函数。

        (3)监听。

                a)将socket套接字由主动变为被动。

                b)创建未完成连接队列、已完成连接队列;未完成连接接经历3次握手才变成已完成连接。

                c)listen函数。

        (4)提取。

                a)从已完成连接队列提取连接,创建一个新的已连接socket套接字用于和客户端通信。

                b)accept函数。

        (5)读写数据。

        (6)关闭socket

客户端:

        (1)创建流式socket套接字

                a)socket函数。

        (2)连接服务器。

                a)指定服务器的IP协议(IPV4或IPV6)、port、IP地址。

                b)connect函数(该函数包含TCP的三次握手)。

        (3)读写数据。

        (4)关闭socket


socket相关结构体和函数

(1)IPV4套接字结构体

#include<netinet/in.h>

struct sockaddr_in {
    sa_family_t    sin_family; /* 协议:AF_INET */
    in_port_t      sin_port;   /* 端口 */
    struct in_addr sin_addr;   /* IP地址 */
};

/* Internet address. */
struct in_addr {
    uint32_t       s_addr;     /* IP地址的网络字节序 */
};

(2)IPV6套接字结构体

#include<netinet/in6.h>

struct sockaddr_in6 {
    sa_family_t     sin6_family;   /* AF_INET6 */
    in_port_t       sin6_port;     /* port number */
    uint32_t        sin6_flowinfo; /* IPv6 flow information */
    struct in6_addr sin6_addr;     /* IPv6 address */
    uint32_t        sin6_scope_id; /* Scope ID (new in 2.4) */
};

struct in6_addr {
    unsigned char   s6_addr[16];   /* IPv6 address */
};

(3)通用套接字结构体

为了接口通用,出现通用套接字结构体。

#include<sys/socket.h>

struct sockaddr {
    sa_family_t sa_family; /* AF_INET 或 AF_INET6 */
    char sa_data[14]; /* address data */
};

(4) socke函数:创建套接字

#include<sys/socket.h>

int socket(int domain, int type, int protocol);
/*
功能:
    创建套接字
参数:
    domain:
        AF_INET
        AF_INET6
        等等
    type:
        SOCK_STREAM:TCP流式套接字
        SOCK_DGRAM:UDP报式套接字
        SOCK_RAW:组包更多
        等等
    protocol:0,自动填充
返回值:
    成功:文件描述符
    失败:-1
*/

(5)connect函数:客户端连接服务器

#include<sys/socket.h>

int connect(int sockfd, const struct sockaddr* addr, socklen_t addrlen);
/*
功能:
    连接服务器
参数:
    sockfd:套接字文件描述符
    addr:IPV4套接字结构体地址 强转为通用套接字结构体
              包含目的主机的IP和port
    addrlen:IPV4套接字结构体大小
返回值:
    成功:0
    失败:-1,并设置errno
        EACCES:权限不足或被防火墙拒绝
        EADDRINUSE:本地地址已被其他套接字使用
        ECONNREFUSED:远程主机拒绝连接
        ETIMEDOUT:连接超时
*/

(6)bind函数:服务器端绑定自己固定的IP和port

#include<sys/socket.h>

int bind(int sockfd, const struct sockaddr* addr, socklen_t addrlen);
/*
功能:
    给套接字sockfd绑定固定的的IP地址和port
参数:
    sockfd:套接字文件描述符
    addr:IPV4套接字结构体地址
    addrlen:IPV4套接字结构体大小
返回值:
    成功:0
    失败:-1
*/

(7)listen函数:服务器端监听是否有连接请求

#include<sys/socket.h>

int listen(int sockfd, int backlog);
/*
功能:
    监听是否有客户端请求连接
参数:
    sockfd:套接字文件描述符
    backlog:已完成连接数量与未完成连接数量之和的最大值,一般写128
返回值:
    成功:0
    失败:-1
*/

(8)accept函数: 从已完成连接队列提取连接

#include<sys/socket.h>

int accept(int sockfd, struct sockaddr* addr, socklen_t* addrlen);
/*
功能:
    从已完成连接队列提取连接
参数:
    sockfd:套接字文件描述符
    addr:IPV4套接字结构体地址,以获取的客户端IP和port信息
    addrlen:存储IPV4套接字结构体大小的变量的地址。
返回值:
    成功:新连接socket的文件描述符
    失败:-1
*/

TCP客户端连接服务器示例:

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

int main() {

    /* 1.创建socket */
    int sock_fd;
    sock_fd = socket(AF_INET, SOCK_STREAM, 0);

    /* 2.连接服务器 */
    // IPV4套接字结构体
    struct sockaddr_in addr;
    // IPV4
    addr.sin_family = AF_INET;
    // 服务器的port,转为网络字节序
    addr.sin_port = htons(8888);
    // 服务器IP地址,转为网络字节序存入addr.sin_addr.s_addr
    inet_pton(AF_INET, "192.168.0.11", &addr.sin_addr.s_addr);
    // 连接
    connect(sock_fd, (struct sockaddr*)&addr, sizeof(addr));

    /* 3.读写数据 */
    char buf[1024] = "";
    while (1) {
        int n = read(STDIN_FILENO, buf, sizeof(buf));  // 从终端读入buf
        write(sock_fd, buf, n);
        n = read(sock_fd, buf, sizeof(buf));
        write(STDOUT_FILENO, buf, n);
    }

    /* 4.关闭 */
    close(sock_fd);

    return 0;
}

运行结果:


TCP服务器端示例:

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

int main(int argc, const char* argv[]) {

    /* 1.创建socket */
    int lfd = socket(AF_INET, SOCK_STREAM, 0);

    /* 2.绑定本机IP地址和port */
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(8000);
    // INADDR_ANY为0,表示绑定通配地址,即本地所有IP地址
    // addr.sin_addr.s_addr = INADDR_ANY; 
    inet_pton(AF_INET, "192.168.124.128", &addr.sin_addr.s_addr);
    int ret = bind(lfd, (struct sockaddr*)&addr, sizeof(addr));
    if(ret < 0) {
        perror("bind");
        exit(0);
    }

    /* 3.监听 */
    listen(lfd, 128);

    /* 4.提取 */
    struct sockaddr_in cliaddr;
    socklen_t len = sizeof(cliaddr);
    int cfd = accept(lfd, (struct sockaddr*)&cliaddr, &len);
    char ip[16] = "";
    printf("新连接到来!IP:%s, port:%d\n", 
                inet_ntop(AF_INET, &(cliaddr.sin_addr.s_addr), ip, 16), 
                ntohs(cliaddr.sin_port));

    /* 5.读写 */
    char buf[1024] = "";
    while (1) {
        bzero(buf, sizeof(buf));
        int n = read(STDIN_FILENO, buf, sizeof(buf));  // 从终端读入buf
        write(cfd, buf, n);
        n = read(cfd, buf, sizeof(buf));
        printf("%s", buf);
    }

    /* 6.关闭 */
    close(lfd);
    close(cfd);

    return 0;
}

运行结果:

注意:服务器进程被杀死,其占用的端口不会立即释放,再次连接会出现如下错误:

因为系统防止短时间内频繁开关相同的端口而将端口设置为TIME_WAIT状态(一般为2MSL,MSL一般为30s),因此TIME_WAIT大概持续60s;过了TIME_WAIT状态才可使用该端口。

若要立即使用该端口,可使用端口复用机制。


补充:

读写时,除了使用read、write函数,还可使用recv和send函数:

#include<sys/types.h>
#include<sys/socket.h>

ssize_t recv(int sockfd, void* buf, size_t len, int flags);

ssize_t send(int sockfd, const void* buf, size_t len, int flags);

http://www.niftyadmin.cn/n/302417.html

相关文章

两数之和 II - 输入有序数组

给你一个下标从 1 开始的整数数组 numbers &#xff0c;该数组已按 非递减顺序排列 &#xff0c;请你从数组中找出满足相加之和等于目标数 target 的两个数。如果设这两个数分别是 numbers[index1] 和 numbers[index2] &#xff0c;则 1 < index1 < index2 < numbers.…

【SpringMVC】二、RequestMapping注解

RequestMapping RequestMapping的功能 ReqeustMapping实现的是将请求与处理请求的控制器方法进行关联&#xff0c;建立映射&#xff0c;实现当请求传进来的时候&#xff0c;调用对应方法的情况。 RequestMapping的位置 注意RequestMapping的value值不可重复&#xff0c;即每…

手机录屏怎么操作?有哪些好用的方法

在现代科技的时代&#xff0c;手机录屏已经成为了常见的操作。这项技术允许我们在手机上录制视频并分享给他人。但是&#xff0c;很多人可能并不知道如何进行手机录屏。下面我们将介绍手机录屏的操作方法和一些值得推荐的工具。 手机录屏操作方法 对于iOS用户&#xff0c;可以…

C++基础之类、对象一(类的定义,作用域、this指针)

目录 面向对象的编程 类的引入 简介 类的定义 简介 访问限定符 命名规则 封装 简介 类的作用域 类的大小及存储模型 this指针 简介 面向对象的编程 C与C语言不同&#xff0c;C是面向对象的编程&#xff0c;那么什么是面向对象的编程呢&#xff1f; C语言编程&#xff0c;规定…

CSS 圆锥渐变+MASK遮罩实现WIFI图标

前言 &#x1f44f;CSS 圆锥渐变MASK遮罩实现WIFI图标&#xff0c;速速来Get吧~ &#x1f947;文末分享源代码。记得点赞关注收藏&#xff01; 1.实现效果 2.实现步骤 定义css变量–bg背景色&#xff0c;–dot弧宽度&#xff0c;–w父元素宽度&#xff0c;–gap&#xff0c…

esp32s3运行一个简单程序一晚, 居然热得自己把自己给烧掉了

c 就运行这个程序一晚上,早上起来发现非常热, 再试已经不能连机了,电流始终0.5安. 就下面这个小程序: /** * 这是主机的程序 */ #include <Wire.h> int num 1; //用来发送给从机 int address 33; //从机地址 void setup() { Serial.begin(115200); if(Wire.begin(…

IPWorks VoIP 2022.0.8505 C++ Edition

IPWorks VoIP IPWorks VoIP 2022 C Edition 支持常见 SIP 和 IVR 操作的简单 VoIP 库。 网络语音组件 IPWorks VoIP 提供 SIP 和 IVR 组件&#xff0c;旨在促进 CTI 应用中的常见 VoIP 操作。快速集成功能&#xff0c;以根据您的自定义 IVR 菜单建立拨出呼叫、接听来电和路由呼…

三分钟上手安全渗透系统Kali Linux

kali linux系统集成了常用的安全渗透工具&#xff0c;省去了安装工具的时间&#xff0c;做安全相关的工作是非常推荐使用的。 安装Kalii Linux 安装系统 一般使用虚拟机进行安装&#xff0c;Kali Linux基于Debian内核&#xff0c;虚拟机的操作系统选择Debian 7.x 64 选择系统…