Linux:网络编程套接字(tcp /udp,网络字节序,大小端,套接字(相关函数),tcp /udp简单的通信端客户端通信代码,三次握手四次挥手)

 

目录

认识ip地址

端口号port:

传输层协议

tcp(传输控制协议)udp(用户数据报协议 )

网络字节序

大小端

判断大小端代码

转换网络端口函数

转换网络ip地址

socket(套接字编程)

使用函数

 struct sockaddr_in

myaddr参数初始化

监听listen()

accept()

一个非常简单的udp服务端程序

服务端:

用户端:

​ 一个非常简单的TCP服务程序

服务端

用户端

多线程的tcp简单聊天程序服务端

三次握手四次挥手


认识ip地址

  • ip :(ip地址在网络上需要具有唯一性)用来标识不同的主机地址
  • 点分十进制
  • 0.0.0.0只能用于接收数据(无法使用),最大为255.255.255.255(广播地址无法使用)
  • ip数据包头部中,有两个ip地址,分别叫源地址,和目的地址
  • IPV4版本的ip地址是4个字节的整数(unsigned int ipaddr)
  • 不够用时的解决方案
    DHCP:动态分配ip地址(谁上网给谁用)
    NAT:地址替换(路由器有唯一的IP地址,连接的个台主机(只有局域网的ip地址)共用这个地址,谁上网给谁用(使用时替换成路由器的外网地址))
  • IPV6版本的ip地址是128字节的地址
  • 不向下兼容IPV4,所有各个软件厂商不愿使用IPV6,所有一直没有推广(没有特殊说明默认就是IPv4)

端口号port:

  • 端口号是一个2字节的16位整数
  • unsigned short port(0~65535,0-1024不推荐使用 )
  • ip地址+端口号 就能够标识网络上的某一台主机的某一个进程
  • 端口号用来标识一个进程,告诉操作系统当前数据需要交给那个进程来处理
  • 一个端口号和一个进程pid都能标识唯一一个进程,那么两者有什么区别?
  • 一个进程可以绑定多个端口号,但是一个端口号不能被多个进程绑定
  • 重启一个程序,进程pid可能会变但是端口号不变
  • (sip dip  sport dport proto:五元组标识了通信)(原地址,目的地址,源端口,目的端口,通信协议)

 

传输层协议

tcp(传输控制协议)udp(用户数据报协议 )

传输层有两个协议,两个协议各有不同的特点和应用场景,如何协议进行数据传输,取决于协议的应用场景和我们当前的使用场景。因此两个协议的特点尤为重要:

  • tcp协议特点:数据的可靠传输,有连接,面向字节流的数据传输(收发书数据比较灵活,数据无明显边界,容易造成pcb粘包问题,发送和接收时就需要小心)
  • udp协议特点:无连接,不可靠 面向数据报数据传输(数据发送的时候有最大长度限制,接受的时候一条一条接收,因为数据有边界,但是不会产生粘包问题)
  • tcp可以保证数据的可靠传输,因此常用于数据安全性要求比较高的场景。但是因为要保证可靠传输,因此牺牲了很多性能,它的数据传输性能是低于udp。
  • udp因为不需要保证可靠传输,所以数据传输速度快,实时性高,常用于传输音乐,视频。对数据的完整性要求不高,但是实时性要求比较高的场景

网络字节序

大小端

凡是存储大于一个字节的数据都必须转换为网络字节序的数据

 

  • 发送主机通常将发送缓冲区的数据按内存地址从低到高的顺序发出
  • 接收主机把从网络上接收到的字节依次保存在接收缓冲区中,也是按内存低地址到高地址保存
  • 因此,网络字节流的地址这样规定:先发的数据是低地址,后发的地址是高地址
  • TCP/IP协议规定,网络数据应该采用大端的字节序,即低地址高字节
  • 不管是大端还是小端,都会按照这个规定
  • 如果发送主机是小端,就需要先转换成大端,否则忽略,直接发送
  • 网络字节序是大端字节序
  • 主机字节序大小端都有可能
  • 大小端(硬件决定)MIPS--PISC处理器(大端),x86处理器(小端)
  • 大端:低地址存高位
  • 小端:低地址存低位

判断大小端代码

#include<stdio.h>
int main()
{
    int a = 1;
    if(((unsigned char*)(&a))[0] == 1)//低地址存低位:小端
    {   
        printf("little\n");
        return 0;
    }   
    else
    {   
        printf("big\n");
        return 0;
    }   
}

转换网络端口函数

#include <arpa/inet.h>

       uint32_t htonl(uint32_t hostlong);四字节

       uint16_t htons(uint16_t hostshort);两字节

主机字节序-网络字节序
       uint32_t ntohl(uint32_t netlong);

       uint16_t ntohs(uint16_t netshort);
网络字节序-主机字节序
h表示host:主机字节序
n:network:网络字节序
l:四字节
s: 二字节

转换网络ip地址

点分十进制转换为网络IP地址

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

       in_addr_t inet_addr(const char *cp);//只能转ipv4
  
      char *inet_ntoa(struct in_addr in);//网络字节序转换从点分十进制字符串

        #include <arpa/inet.h>

       int inet_pton(int af, const char *src, void *dst);//可以转换ipv4和ipv6

socket(套接字编程)

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

想要开始网络编程,就需要先创建一个套接字(网络编程,第一步永远是创建套接字,套接字创建成功后,我们才可以通过对套接字的操作,才完成网络是数据的传输)

网络通信是网络上两个主机的进程间的通信,这连个主机有一个区分,

一个叫客户端,一个叫服务端,并且永远是客户端首先向服务端发起请求

也就是,服务端在明处,客户端在暗处。比如qq,客户端非常多,腾讯的服务器不知道客户是谁在哪,所以没法向客户端推送消息,只要下载了客户端,

这些服务器信息都已经封装在qq软件中了

使用函数

 

 struct sockaddr_in

struct sockaddr_in这个结构体是ipv4版本的地址信息,包含地址域,端口,ip

地址域(地址类型):AF_INET,AF_INET6

myaddr参数初始化

  1. 将结构体清零
  2. 设置地址类型为:AF_INET
  3. 网络地址为INADDR_ANY
    这个宏表示本地的任意ip地址,直到与某个客户端建立了连接时才确定到底用哪个ip地址
  4. 端口号为SERV_PORT,我们定义为:9000

监听listen()

 #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

       int listen(int sockfd, int backlog);
  • listen声明的socket处于监听状态,并且允许最多有backlog个客户端处于连接等待状态
  • 如果收到更多的请求连接就忽略,一般不会设置太大(一般是5)
  • 成功返回0,失败返回-1

accept()

#include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

       int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
  •  三次握手完成后,服务器调用accept()接收连接
  • 如果还没有请求,就阻塞等待直到有客户端到来
  • 如果给addr传NULL,表示不关心客户端地址
  • addrlen参数是一个传入传出参数,传入的是调用者提供的缓冲区addr
    的长度,以避免缓冲区溢出的问题,传出的是客户端地址结构体实际长度

一个非常简单的udp服务端程序

服务端:

/*  这是一个非常简单的udp服务端程序
 *  功能是:客户端与服务端的聊天程序
 * 
 */

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

int main()
{
    //1. 创建套接字
    //int socket(int domain, int type, int protocol);
    //  domain: 地址域
    //      AF_INET     ipv4协议
    //  type:  套接字类型
    //      SOCK_STREAM 流式套接字    
    //      SOCK_DGRAM  数据报套接字
    //  protocol:   协议类型
    //      0-默认;流式套接字默认tcp协议,数据报套接字默认udp协议
    //  返回值:套接字描述符,失败:-1
    int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (sockfd < 0) {
        perror("socket error!!\n");
        return -1;
    }
    //struct sockaddr 
    //struct sockaddr_in
    //struct sockaddr_in6
    //2. 为socket绑定地址信息,确定socket能够操作缓冲区中的哪些数据
    //  int bind(int sockfd, struct sockaddr *addr,socklen_t addrlen);
    //      sockfd: 套接字描述符
    //      addr:  要绑定的地址信息
    //      addrlen:地址信息的长度
    //
    //      端口号是0-65535之间的的一个数字,0-1024不推荐使用
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(9000);
    addr.sin_addr.s_addr = inet_addr("192.168.3.32");
    //inet_pton(AF_IENT, "192.168.122.132", &addr.sin_addr);
    socklen_t len = sizeof(struct sockaddr_in);
    int ret = bind(sockfd, (struct sockaddr*)&addr, len);
    if (ret < 0) {
        perror("bind error");
        close(sockfd);
        return -1;
    }
    //3.接收数据
    //ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags
    //      struct sockaddr *src_addr, socklen_t *addrlen);
    //      sockfd: socket描述符
    //      buf:   用于将存储接收的数据
    //      len:   想要接收的数据长度
    //      flags: 0-默认是说如果缓冲区没有数据,那么我就阻塞等待
    //      src_addr:  用于确定数据的发送端地址信息
    //      addrlen:   地址信息的长度
    //      返回值:实际接收的数据长度 ,-1:失败
    while (1) {
        //接收数据
        char buff[1024] = {0};
        struct sockaddr_in cli_addr;
        len = sizeof(struct sockaddr_in);
        ssize_t rlen = recvfrom(sockfd, buff, 1023, 0,
                (struct sockaddr*)&cli_addr, &len);
        if (rlen < 0) {
            perror("recvfrom error");
            close(sockfd);
            return -1;
        }
        printf("client[%s:%d] say:%s\n", 
                inet_ntoa(cli_addr.sin_addr),
                ntohs(cli_addr.sin_port), buff);
        //发送数据
        //ssize_t sendto(int sockfd, const void *buf, size_t len, 
        //  int flag, struct sockaddr *dest_addr,socklen_t addrlen)
        //  sockfd: socket描述符,发送数据的时候就是通过这个socket
        //      所绑定的地址来发送
        //  buf:   要发送的数据
        //  len:   要发送的数据长度
        //  flag:  0-默认阻塞式发送
        //  dest_addr: 数据要发送到的对端地址
        //  addrlen:   地址信息长度
        //  返回值:返回实际的发送数据长度,失败返回-1
        memset(buff, 0x00, 1024);
        scanf("%s", buff);
        sendto(sockfd, buff, strlen(buff), 0,
                (struct sockaddr*)&cli_addr, len);
    }
    close(sockfd);
    return 0;
}

用户端:

//这是一个udp简单的网络聊天的客户端程序
//1:创建套接字
//2:绑定地址信息
//3:向服务端发送数据
//4:接收服务端返回的数据
//5: 关闭socket

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
int main()
{
    //创建套接字
    int sockfd = socket(AF_INET,SOCK_DGRAM,0);
    if(sockfd < 0)
    {
        perror("socket");
        return -1;
    }
    //2:为套接字绑定地址信息,通常我们不推荐手动绑定地址信息
    //因为绑定有可能会因为特殊情况失败,但是客户端发送数据的时候
    //具体使用哪个地址和端口都无所谓,只要数据能成功发送就行
    //所以我们不手动绑定地址,直到发送数据的时候,操作系统检测
    //到socket没有绑定地址,会自动选择合适的地址和端口为
    //socket绑定地址,这种绑定一般不会出搓
    struct 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.5.129");
    socklen_t len = sizeof(struct sockaddr_in);
    while(1)
    {
        //3: 发送
        char buff[1024] = {0};
        scanf("%s",buff);
        sendto(sockfd,buff,strlen(buff),0,
                (struct sockaddr*)&serv_addr,len);

        //4:接受
        memset(buff ,0x000,1024);
        ssize_t r_len = recvfrom(sockfd,buff,1023,0,
                (struct sockaddr*)&serv_addr,&len);
        if(r_len < 0)
        {
            perror("recvfrom");
            return -1;
        }
        printf("server say:%s\n",buff);
    }
    close(sockfd);
    return 0;
}

 一个非常简单的TCP服务程序

服务端

/*  这是一个tcp服务端程序,它的功能是简单的服务端聊天程序
 *  tcp是有连接的面向字节流的可靠传输
 *      1. 创建socket
 *      2. 为socket绑定地址
 *      3. 开始监听:可以开始接收客户端的连接请求
 *      4. 获取连接建立成功的新socket:
 *      5. 接收数据
 *      6. 发送数据
 *      7. 关闭socket
 *  监听socket和后边新建立的socket的关系
 */
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>

int main(int argc, char *argv[])
{
    if (argc != 3) {
        printf("Usage: ./tcp_server ip port\n");
        return -1;
    }
    //1. 创建socket
    int sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (sockfd < 0) {
        perror("socket error");
        return -1;
    }
    //2. 为socket绑定地址信息
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(atoi(argv[2]));
    addr.sin_addr.s_addr = inet_addr(argv[1]);
    socklen_t len = sizeof(struct sockaddr_in);
    int ret = bind(sockfd, (struct sockaddr*)&addr, len);
    if (ret < 0) {
        perror("bind error");
        return -1;
    }
    //3. 监听
    //  int listen(int sockfd, int backlog);
    //  sockfd:     socket描述符
    //  backlog:   最大的同时并发连接数
    if (listen(sockfd, 5) < 0) {
        perror("listen error");
        return -1;
    }
    while(1) {
        int new_sockfd;
        struct sockaddr_in cli_addr;
        len = sizeof(struct sockaddr_in);
        //4. 获取连接成功的socket
        //int accept(int sockfd, struct sockaddr *addr, 
        //      socklen_t *addrlen);
        //  sockfd: socket描述符
        //  addr:  新建立连接的客户端地址信息
        //  addrlen:地址信息长度
        //  返回值:返回新的socket连接描述符,失败:-1
        
        //  new_sockfd 与 lst_sockfd 区别
        //  lst_sockfd这是一个拉皮条的socket,说所有的连接请求的数
        //      据都是发送到这个socket的缓冲区,然后进行处理(会为
        //      这个新连接的客户端新建一个socket)
        //      (这个socket接收到的数据都是连接请求),
        //      (dip dport)
        //  new_sockfd这是一个干活的socket,连接建立成功之后,这个s
        //      ocket有自己的缓冲区,往后这个客户端所发送的数据都
        //      是在这个socket的缓冲区中。
        //      (dip dport sip sport)
        //  accept函数是一个阻塞型的函数,连接成功队列中如果没有新
        //  的连接,那么就会一直阻塞直到有新的客户端连接到来
        new_sockfd = accept(sockfd, (struct sockaddr*)&cli_addr, 
                &len);
        if (new_sockfd < 0) {
            perror("accept error");
            continue;
        }
        printf("new conn %s:%d\n", inet_ntoa(cli_addr.sin_addr),
                ntohs(cli_addr.sin_port));
        while(1) {
            //5. 接收数据
            //ssize_t recv(int sockfd, void *buf, size_t len,int flags)
            //  sockfd: 建立连接成功的socket描述符
            //  buf:   用于接收数据
            //  len:   用于指定接收数据长度
            //  flags:默认0-阻塞式接收
            //  返回值:错误:-1    连接关闭:0     实际接收长度:>0
            char buff[1024] = {0};
            ssize_t rlen = recv(new_sockfd, buff, 1023, 0);
            if (rlen < 0) {
                perror("recv error");
                close(new_sockfd);
                continue;
            }else if (rlen == 0) {
                printf("peer shutdown!!\n");
                close(new_sockfd);
                continue;
            }
            printf("client[%s:%d] say:%s\n", 
                    inet_ntoa(cli_addr.sin_addr),
                    ntohs(cli_addr.sin_port), buff);
            //发送数据
            memset(buff, 0x00, 1024);
            scanf("%s", buff);
            send(new_sockfd, buff, strlen(buff), 0);
        }
    }
    close(sockfd);
    return 0;
}

用户端

/*  这是一个tcp的客户端简单聊天程序
 *      1. 创建socket
 *      2. 为socket绑定地址
 *      3. 向服务端发起连接请求
 *      4. 发送数据
 *      5. 接收数据
 *      6. 关闭
 */
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>

int main(int argc, char *argv[])
{
    if (argc != 3) {
        printf("Usage:./tcp_client ip port\n");
        return -1;
    }
    //1. 创建socket
    int sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (sockfd < 0) {
        perror("socket error");
        return -1;
    }
    //2. 绑定:客户端程序不推荐绑定地址
    //3. 向服务端发起连接请求
    //int connect(int sockfd, struct sockaddr *addr,
    //      socklen_t addrlen);
    //  sockfd: socket描述符
    //  addr:  要连接的服务端地址
    //  addrlen:   地址信息长度
    //  返回值:成功:0 失败:-1
    struct sockaddr_in serv_addr;
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(atoi(argv[2]));
    serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
    socklen_t len = sizeof(struct sockaddr_in);
    int ret = connect(sockfd, (struct sockaddr*)&serv_addr, len);
    if (ret < 0) {
        perror("connect error");
        return -1;
    }
    while(1) {
        //4. 发送数据
        char buff[1024] = {0};
        scanf("%s", buff);
        send(sockfd, buff, strlen(buff), 0);
        //5. 接收数据
        memset(buff, 0x00, 1024);
        ssize_t rlen = recv(sockfd, buff, 1023, 0);
        if (rlen < 0) {
            perror("recv error");
            return -1;
        }else if (rlen == 0) {
            printf("peer shutdown!\n");
            return -1;
        }
        printf("server say:%s\n", buff);
    }
    close(sockfd);
    return 0;
}

 

多线程的tcp简单聊天程序服务端

//这是一个多线程的tcp简单聊天程序服务端
//1:创建socket
//2:绑定地址
//3:开始监听
//4:获取客户端的socket连接
//5:创建线程
//在线程中获取socket连接与客户端进行通信
//6:关闭socket
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<unistd.h>
#include<string.h>
#include<sys/socket.h>
#include<pthread.h>
#include<netinet/in.h>
#include<arpa/inet.h>
void* thr_start(void *arg)
{
    int clifd = (int)arg;
    while(1)
    {   
        char buff[1024] = {0};
        ssize_t ret = recv(clifd,buff,1024,0);
        if(ret <= 0)
        {   
            perror("recv");
            close(clifd);
            break;
        }   
 printf("client say:[%s]\n",buff);
        send(clifd,"what???",7,0);
    }
    return NULL;
}
int create_worker(int clifd)
{
    //创建一个线程来单独处理与指定客户端的通信
    pthread_t tid;
    int ret;
    ret = pthread_create(&tid,NULL,thr_start,(void*)clifd);
    if(ret != 0)//0成功非0失败
    {
        printf("pthread error\n");
    }
    return -1;
}
int main(int argc,char* argv[])
{
    int lstfd ,clifd,ret;
    socklen_t len;
    struct sockaddr_in lst_addr;
    struct sockaddr_in cli_addr;

    if(argc != 3)
    {
        printf("Usage:./ ip port");
        return -1;
 }
    lstfd = socket(AF_INET,SOCK_STREAM,0);
    if(lstfd < 0)
    {
        perror("lstfd");
        return -1;
    }
    lst_addr.sin_family = AF_INET;
    lst_addr.sin_port = htons(atoi(argv[2]));
    lst_addr.sin_addr.s_addr = inet_addr(argv[1]);
    len =sizeof(struct sockaddr_in);

    ret = bind(lstfd,(struct sockaddr*)&lst_addr,len);
    if(ret < 0)
    {
        perror("bind");
        return -1;
    }
    if(listen(lstfd,5) < 0)
    {
        perror("listen");
        return -1;
    }
    while(1)
    {
        clifd = accept(lstfd,(struct sockaddr*)&cli_addr,&len);
        if(clifd < 0)
        {
            perror("accept error");
            continue;
      }
        create_worker(clifd);
    }
    return 0;
}

客户端就不敲了,上面的客户端可以用在这里

三次握手四次挥手

https://blog.csdn.net/W_J_F_/article/details/84790841

三次挥手和四次握手详解

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值