基于Linux套接字编程

11 篇文章 0 订阅
4 篇文章 0 订阅

概念

socket是一种常见的网络编程,使用socket可以实现不同主机间的通信。下面简单理解一下socket编程的大致流程
在这里插入图片描述

本篇文章以tcp为例介绍socket的使用

接口介绍

socket();

在这里插入图片描述
创建一个套接字,可以使用该套接字进行通信,事实上就是创建了一块缓冲区,OS层面认为它是一个文件,所以返回值是一个文件描述符,通过描述符来操作套接字。
在这里插入图片描述

成功返回文件描述符,失败返回-1。

参数:
第一个参数:在这里插入图片描述
使用哪一个代表使用哪种方式,一般使用AF_INET(ipv4)

第二个参数:
在这里插入图片描述

常用的有两个:SOCK_STREAM和SOCK_DGRAM,分别是面向字节流和面向数据报,一个供tcp用,一个供udp使用。

第三个参数:协议的配置,一般使用0。

bind();

在这里插入图片描述
将本地的ip地址和端口号与刚才创建的socket绑定起来。ip地址在全网中标识唯一一台主机,端口号在一台主机中标识唯一一个进程,所以ip地址加端口号标识了全网内唯一一个进程,将该进程与socket绑定起来,就可以与指定主机的指定进程实现通信。

成功返回0,失败返回-1。

参数:
第一个参数:上一步创建好的socket。
第二个参数:一个结构体,结构体保存要绑定的ip地址和端口号还有要使用的协议,要传入的参数是struct sockaddr* ,但是在使用时不能定义该类型。常使用的有两个:struct sockaddr_in与struct sockaddr_un。ipv4使用struct sockaddr_in,传入时强转为struct sockaddr*。
第三个参数:传入的addr的长度。

listen();

让服务器处于监听状态,因为使用tcp在进行通信前两个主机要先进行链接,确定链接后才能实现通信,监听状态就是让服务器开始等待另一个主机发出链接请求,服务器可以直接相应。
在这里插入图片描述

让服务器处于监听状态,便于随时接收链接请求

返回值:成功返回0,失败返回-1。

参数:
第一个参数:要监听的套接字。
第二个参数:请求队列的长度。
请求队列
当套接字正在处理客户端请求时,如果有新的请求进来,套接字是没法处理的,只能把它放进缓冲区,待当前请求处理完毕后,再从缓冲区中读取出来处理。如果不断有新的请求进来,它们就按照先后顺序在缓冲区中排队,直到缓冲区满。这个缓冲区,就称为请求队列(Request Queue)。

缓冲区的长度(能存放多少个客户端请求)可以通过 listen() 函数的 backlog 参数指定,但究竟为多少并没有什么标准,可以根据你的需求来定,并发量小的话可以是10或者20。

如果将 backlog 的值设置为 SOMAXCONN,就由系统来决定请求队列长度,这个值一般比较大,可能是几百,或者更多。

当请求队列满时,就不再接收新的请求,对于 Linux,客户端会收到 ECONNREFUSED 错误

connect();

在这里插入图片描述
请求建立链接

返回值:链接成功返回0,失败返回-1

参数:
第一个参数:创建好的套接字
第二个参数:一个结构体,包括使用的协议与要链接的ip地址和端口号,要传入的参数是struct sockaddr* ,但是在使用时不能定义该类型。常使用的有两个:struct sockaddr_in与struct sockaddr_un。ipv4使用struct sockaddr_in,传入时强转为struct sockaddr*。
第三个参数:要传入的addr的长度

accept();

在这里插入图片描述
接受链接,与listen不同,listen只是让服务器处于监听状态,可以收到请求,但是与客户端建立链接的是accept函数。

返回值:成功返回一个文件描述符,此后服务器与客户端进行通信时使用的就是该描述符。失败返回-1。

参数:
第一个参数:创建好的套接字。
第二个参数:输出型参数,保存了客户端(请求建立链接的一方)的ip地址等信息。
第三个参数:输出型参数,addr的长度。

send();

发送信息,由于这是对文件描述符进行操作,使用系统调用接口write和read也可以实现发送和接受的任务而不是必须为send和recv。
在这里插入图片描述

返回值:实际发送了多少个字符

参数:
第一个参数:创建好的套接字
第二个参数:要发送的字符
第三个参数:期望发送多少个字符
第四个参数:一般使用置0即可。

recv();

在这里插入图片描述

接收信息

返回值:实际接收到了多少个字符

参数:
第一个参数:创建好的套接字
第二个参数:存放字符的缓冲区
第三个参数:期望接受多少个字符
第四个参数:一般使用置0即可

客户端与服务器端通信模型

这里添加了一些细节,服务端收到请求连接时创建新的线程处理链接,主线程依旧处于等待链接状态,这样就可以实现多个客户端与服务器同时通信了。
tcpServer.hpp

#pragma once
#include <iostream>
#include <sys/socket.h>
#include <stdlib.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <signal.h>
#include <string.h>

#define BACKLOG 5


class tcpServer{
    private:
        int _port;
        int _sock;
    public:
        tcpServer(int port):_port(port){}
        void init(){
            _sock = socket(AF_INET, SOCK_STREAM, 0);//创建套接字
            if(_sock < 0){
                std::cerr << "socket";
                exit(1);
            }
            sockaddr_in addr;//配置要使用的协议以及IP地址和端口号信息
            addr.sin_family = AF_INET;//使用IPv4
            addr.sin_addr.s_addr = htonl(INADDR_ANY);//关联本主机所有IP地址
            addr.sin_port = htons(_port);//指定端口号
            if(bind(_sock, (sockaddr*)&addr, sizeof(addr)) < 0){//绑定套接字
                std::cerr << "bind";
                exit(2);
            }
            if(listen(_sock, BACKLOG) < 0){//将服务器设置为监听状态,随时等待客户端链接
                std::cerr << "listen";
                exit(3);
            }
        }

        ~tcpServer(){
            close(_sock);
        }
        
        static void* run(void* arg){
            pthread_detach(pthread_self());//线程分离
            char buf[64];
            while(true){
                ssize_t ret = recv(*(int*)arg, buf, sizeof(buf) - 1, 0);//读取信息
                if(ret > 0){
                    buf[ret] = 0;
                    if(strcmp(buf, "quit") == 0){//客户端退出,该线程退出
                        std::cout << "I got quit!" << std::endl;
                        pthread_exit(0);
                    }
                    std::cout << buf << std::endl;
                    std::string echo = buf;
                    echo += "(Server echo)";
                    if(send(*(int*)arg, echo.c_str(), echo.size(), 0) < 0){//向客户端发送回显消息
                        std::cerr << "send";
                        exit(4);
                    }
                }
            }
            
        }

        void start(){
            sockaddr_in addr;
            socklen_t len;
            signal(SIGCHLD, SIG_IGN);//父进程默认处理signal信号
            while(true){
                int sock = accept(_sock, (sockaddr*)&addr, &len);//接收链接
                if(sock < 0){//没有接收到链接,重新接收
                    continue;
                }
                else{
                    std::cout << "get a new link" << std::endl;//收到链接,建立链接,开始通信
                    pthread_t tid;
                    pthread_create(&tid, nullptr, run, (void*)&sock);//创建新的线程执行任务
                }
            }
        }
};


tcpServer.cc

#include "tcpServer.hpp"

int main(int argc, char*argv[]){
    if(argc != 2){
        std::cout << "Please Enter Port!" << std::endl;
        exit(1);
    }
    tcpServer* p = new tcpServer(atoi(argv[1]));//输入端口号
    p->init();//服务器初始化
    p->start();//服务器启动

    delete p;
    return 0;
}

tcpClient.hpp

#pragma once
#include <iostream>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>

class tcpClient{
    private:
        int _sock;
        int _port;
        std::string _ip;
    public:
        tcpClient(std::string ip, int port):_port(port),_ip(ip){}//初始化ip和端口号
        ~tcpClient(){
            close(_sock);
        }

        void init(){
            _sock = socket(AF_INET, SOCK_STREAM, 0);//创建套接字
            if(_sock < 0){
                std::cerr << "socket";
                exit(1);
            }
        }

        void start(){
            sockaddr_in addr;
            addr.sin_family = AF_INET;
            addr.sin_addr.s_addr = inet_addr(_ip.c_str());
            addr.sin_port = htons(_port);//配置IP地址与端口号和协议
            if(connect(_sock, (sockaddr*)&addr, sizeof(addr)) < 0){//建立链接
                std::cerr << "connect";
                exit(2);
            }

            char buf[64];
            while(true){
                ssize_t ret = read(0, buf, sizeof(buf) - 1);//从标准输入读取字符放入缓冲区(cin不可以读取空格)
                if(ret > 0){
                    buf[ret - 1] = 0;
                    if(strcmp(buf, "quit") == 0){//查看是否为退出标志
                        break;
                    }
                    send(_sock, buf, ret, 0);//不是退出标志,将信息发送过去
                    ret = recv(_sock, buf, sizeof(buf), 0);//接受服务器发送的返回信息
                    buf[ret] = 0;
                    std::cout << buf << std::endl;
                }
            }
        }
};

tcpClient.cc

#include "tcpClient.hpp"

int main(int argc, char*argv[]){
    if(argc != 3){
        std::cout << "Please enter ip and port!" << std::endl;
        exit(1);
    }
    tcpClient* p = new tcpClient(argv[1], atoi(argv[2]));//传入IP地址与端口号
    p->init();
    p->start();

    delete p;

    return 0;
}

运行实例

在这里插入图片描述

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值