套接字编程

一、基本概念

ip 地址:在网络中标识一台唯一的主机,是无符号4个字节的整数

端口:在主机上标识一个进程,一个进程可以使用多个端口,但一个端口只能被一个进程使用

五元组:源 ip 地址,目的 ip 地址,源端口,目的端口,通信协议

网络字节序:CPU在对数据内存中进行存取时候的方向分出了大小端,MIPS -- RICS 处理器 -- 大端(低地址存高位),x86 处理器 -- 小端(低地址存低位),网络数据传输时,凡是存储大于一个字节的的数据都必须转换为网络字节序,网络字节序是大端字节序。

传输层协议:tcp、udp

socket 套接字编程:socket 是一套接口,用于网络编程的接口,同时 socket 也是一个数据结构

流程

(1)创建套接字:建立与网卡的联系,协议版本的选择,传输层协议的选择

(2)为套接字绑定地址信息:ip,port 

(3)接收数据

(4)发送数据

(5)关闭 socket

二、udp协议

1. 几个小知识点

源端口,目的端口:负责端与端之间的数据传输

数据报长度:一个 udp 数据报的长度是64k,当我们发送的数据超过64k 时,数据将丢失,这使得我们发送大于64k 的数据时,要在应用层进行数据分包(udp 头部的长度是8个字节:65535 - 8)

校验和(二进制反码求和):检验 udp 数据的完整性及正确性

2. 特性

无连接,不可靠,面向数据报(数据发送的时候有最大长度的限制,不会产生粘包问题)数据传输,传输时对数据的的完整性要求不高,数据传输速度快,实时性高,常用于传输音乐、视频等

3. 代码实现   

server.c

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

int main()
{
    /*
     * 函数名: int socket(int dointmain, int type, int protocol);
     * 函数功能: 创建套接字
     * 参数: domain:地址域      AF_INET 使用IPV4网络协议
     *       type: 套接字类型
     *       SOCK_STREAM 流式套接字     默认协议tcp
     *       SOCK_DGRAM  数据报套接字   默认协议udp
     * protocol:协议
     * 返回值:套接字描述符(非负整数)    失败:-1
    */
    int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (sockfd <0)
    {
        perror("scoket error");
        return -1;
    }
    /*
     * 函数名: int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen);
     * 函数功能: 为套接字绑定地址信息
     * 参数: sockfd:套接字描述符
     *       addr:地址信息
     *          使用sockaddr_in定义,使用时进行类型强转
     *       adrlen:地址信息长度
     * 返回值: 0     失败:-1
    */
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    /*
     * 函数名: uint16_t htons(uint16_t hostshort);
     * 函数功能: uint16_t类型的数据从主机字节序转换为网络字节序
    */
    addr.sin_port = htons(9000);
    /*
     * 函数名: int32_t htonl(uint32_t hostlong);
     * 函数功能: uint32_t类型的数据从主机字节序转换为网络字节序
    */
    // addr.sin_addr.s_addr = htonl(0xc0a8f18b);
    /*
     * 函数名: in_addr_t inet_addr(const char *cp);
     * 函数功能: 将点分十进制的字符串IP地址转换为网络字节序IP地址
    */
    /
    // 另一种写法
    addr.sin_addr.s_addr = inet_addr("192.168.241.139");
    socklen_t len = sizeof(struct sockaddr_in);
    int ret;
    ret = bind(sockfd, (struct sockaddr*)&addr, len);
    if (ret < 0)
    {
        perror("bind error\n");
        return -1;
    }
    /*
     * 函数名: ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
     *                          struct sockaddr *src_addr, socklen_t *addrlen);
     * 函数功能: 接收数据
     * 参数: sockfd:套接字描述符    buf:接收数据
     *       len:接收数据的长度     flags:默认赋0,阻塞接受(没数据就一直等待)
     *       src_addr:数据从哪里来,对端地址信息
     *       addrlen:地址信息长度(输入输出复合参数)
     * 返回值: 实际接收数据长度     失败:-1
    */
    while (1)
    {
        char buff[1024] = {0};
        struct sockaddr_in cli_addr;
        recvfrom(sockfd, buff, 1023, 0, (struct sockaddr*)&cli_addr, &len);
        printf("client say: %s\n", buff);
        memset(buff, 0x00, 1024);
        scanf("%s", buff);
        /*
         * 函数名: ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
         *                        const struct sockaddr *dest_addr, socklen_t addrlen);
         * 函数功能: 发送数据
         * 参数: dest_addr:目的端地址信息   addrlen:地址信息长度
        */
        sendto(sockfd, buff, strlen(buff), 0, (struct sockaddr*)&cli_addr, len);
    }
    close(sockfd);
    return 0;
}

client.c

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

class UdpSocket
{
    public:
        UdpSocket():m_sockfd(-1){}
    public:
        bool Socket()
        {
            m_sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
            if (m_sockfd < 0)
            {
                perror("socketr error");
                return false;
            }
            return true;
        }
        bool Bind(string &ip, uint16_t port)
        {
            sockaddr_in addr;
            addr.sin_family = AF_INET;
            addr.sin_port = htons(port);
            addr.sin_addr.s_addr = inet_addr(ip.c_str());
            socklen_t len = sizeof(sockaddr_in);
            int ret = bind(m_sockfd, (sockaddr*)&addr, len);
            if (ret < 0)
            {
                perror("bind error");
                return false;
            }
            return true;
        }
        ssize_t Recv(char *buf, size_t len, sockaddr_in *addr = NULL)
        {
            int ret;
            sockaddr_in _addr;
            socklen_t addr_len = sizeof(sockaddr_in);
            ret = recvfrom(m_sockfd, buf, len, 0, (sockaddr*)&_addr, &addr_len);
            if (addr)
            {
                memcpy((void *)addr, (void*)&_addr, addr_len);
            }
            return ret;
        }
        ssize_t Send(const char *buf, size_t len, sockaddr_in *addr)
        {
            int ret;
            socklen_t addr_len = sizeof(sockaddr_in);
            ret = sendto(m_sockfd, buf, len, 0, (sockaddr*)addr, addr_len);
            return ret;
        }
        bool Close()
        {
            if (m_sockfd != -1)
            {
                close(m_sockfd);
                m_sockfd = -1;
            }
            return true;
        }
    private:
        int m_sockfd;
};

#define CHECK_RET(q) if (false == (q)){return -1;}
int main()
{
    UdpSocket sock;
    CHECK_RET(sock.Socket());
    sockaddr_in serv_addr;
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(9000);
    serv_addr.sin_addr.s_addr = inet_addr("192.168.241.139");
    while (1)
    {
        string str;
        cin >> str;
        sock.Send(str.c_str(), str.length(), &serv_addr);
        char buff[1024] = {0};
        sock.Recv(buff, 1024);
        printf("serve say: %s\n", buff);
    }
    sock.Close();
}

三、tcp

1. 特性

数据可靠传输,有连接,面向字节流(收发数据的时候比较了灵活,数据无明显边界,容易造成粘包问题)数据传输,传输性能比 udp 弱、

可靠传输:确认应答机制(每条数据都需要进行回复确认保证送达)超时重传机制(超时等待没有回复,则认为对方没有收到,因此重传 500 ms)序号与确认序号(保证数据有序到达)

滑动窗口机制:通过窗口大小进行流量控制,并且压缩等待时间

滑动窗口的快速重传机制:收到后续 ack,认为前边成功;收到后边数据,则连发三条没收到的数据重传请求

拥塞窗口机制:发送端定义拥塞窗口大小,发送数据从小到多(指数增加),达到阈值之后平缓增加,出现丢包情况重新开始

延迟应答机制:尽可能使窗口足够大,以提高传输吞吐量

2. 代码实现

server.c

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

void serviceIO(int sock)
{
    char buf[1024];
    while (1)
    {
        ssize_t s = read(sock, buf, sizeof(buf)-1);
        if (s > 0)
        {
            buf[s] = 0;
            printf("client# %s\n", buf);
        }
        else if (0 == s)
        {
            printf("client quit!!!\n");
            break;
        }
        else 
        {
            perror("read error");
            break;
        }
    }
    close(sock);
}

void *service(void *arg)
{
    int sock = (int)arg;
    serviceIO(sock);
}

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        printf("Usage:%s [ip] [port]\n", argv[0]);
        return 1;
    }

    int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_sock < 0)
    {
        perror("socket error");
        return 2;
    }

    struct sockaddr_in local;
    local.sin_family = AF_INET;
    local.sin_port = htons(atoi(argv[2]));
    local.sin_addr.s_addr = inet_addr(argv[1]);

    if (bind(listen_sock, (struct sockaddr*)&local, sizeof(local)) < 0)
    {
        perror("bind error");
        return 3;
    }
    if (listen(listen_sock, 5) < 0)
    {
        perror("listen erroe");
        return 4;
    }

    for (; ; )
    {
        struct sockaddr_in client;
        socklen_t len = sizeof(client);
        int sock = accept(listen_sock, (struct sockaddr*)&client, &len);
        if (sock < 0)
        {
            perror("accept error");
            continue;
        }
        printf("get new link [%s:%d]...!\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
        pthread_t id;
        pthread_create(&id, NULL, service, (void*)sock);
        pthread_detach(id);
   }
    return 0;
}

client.c

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

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        printf("Usage:%s ip port\n", argv[0]);
        return 1;
    }
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0)
    {
        perror("socket error");
        return 2;
    }
    struct sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_port = htons(atoi(argv[2]));
    server.sin_addr.s_addr = inet_addr(argv[1]);
    if (connect(sock, (struct sockaddr*)&server, sizeof(server)) < 0)
    {
        perror("connect error");
        return 3;
    }
    char buf[1024];
    while (1)
    {
        printf("Please enter:");
        scanf("%s", buf);
        write(sock, buf, strlen(buf));
    }
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值