欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Socket套接字编程

程序员文章站 2022-03-18 19:14:09
...

前天面试了环信公司,在面试的时候,感觉面试官很亲切,临走的时候叫我回去再把TCP和UDP网络编程在看看,并且给他发过去,我知道现在的水平确实还有很大提升的空间,我也会一直不断的学习,加强自身的知识水平建设。

(1)基于UDP协议的socket套接字编程
UDP协议是非链接的协议,它不与对方建立连接,而是直接把要发送的数据发送给对方。所以UDP协议适用于一次传输数据量很少,对可靠性要求不高的应用场景。但是正是因为udp协议没有类似于tcp的三次握手,可靠传输机制等,所以效率比较高。
UDP协议的应用也非常广泛,比如知名的应用层协议:SNMP,DNS都是基于UDP的。

UDP通信的流程比较简单,因此要搭建这么一个常用的UDP通信框架也是比较简单的。以下是UDP的框架图。
Socket套接字编程

由以上框图可以看出,客户端要发起一次请求,仅仅需要个步骤(socket和sendto),而服务器端也仅仅需要三个步骤即可接收到来自客户端的消息(socket、bind、recvfrom)。

  • 1.socket()函数
    #include <sys/types.h>          
    #include <sys/socket.h>
    int socket(int domain, int type, int protocol);
  • domain 如果是IPv4就写AF_INET,如果是IPV6就是AF_INET6.

  • type 如果是TCP就写SOCK_STREAM,UDP就写SOCK_DGRAM。

  • protocol 填0就可以了。因为通过前面两个参数一般就可以确定后面的这个是什么类型,前面填了AF_INET,SOCK_DGRAM 适用的就是UDP了。

-2. bind()函数

这里服务器调用就可以了,将本机IP地址,选定的一个端口与前面的套接字绑定。客户端的话,就可以不用了。

  int bind(SOCKET sock, const struct sockaddr *addr, int addrlen);  

如果bind()返回成功,就会返回0。
如果返回不成功,可以调用GetLastError() 函数,通过错误编码查看是什么错误。
  1. 数据的接收和发送
    UDP在服务器绑定地址后,就可以直接发送接受消息了。当然客户端创建成功套接字后也就可以直接接受和发送了。 一般使用sendto(),recvfrom().
int sendto (int s, const void *buf, int len, unsigned int flags, const struct sockaddr *to, int tolen);
int recvfrom(int s, void *buf, int len, unsigned int flags, struct sockaddr *from, int *fromlen);
  • s: socket描述符。
  • buf: UDP数据报缓存地址。
  • len: UDP数据报长度。
  • flags: 该参数一般为0。
  • to: sendto()函数参数,struct sockaddr_in类型,指明UDP数据发往哪里报。
  • tolen: 对方地址长度,一般为:sizeof(struct sockaddr_in)。
  • from: recvfrom()函数参数,struct sockaddr_in类型,指明从哪里接收UDP数据报。
  • fromlen: 对方地址长度,一般为:sizeof(struct sockaddr_in)。
    - 函数返回值
    1.对于sendto()函数,成功则返回实际传送出去的字符数,失败返回-1,错误原因存于errno 中。
    2.对于recvfrom()函数,成功则返回接收到的字符数,失败则返回-1,错误原因存于errno中。

以下是UDP网络编程的一个小栗子。

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



//服务器程序,./server [ip] [port]
//            ./server 127.0.0.1 9090
int main(int argc,const char* argv[])
{
    if(argc < 3)
    {
        printf("input error!\n");
        return 1;
    }
    int fd = socket(AF_INET,SOCK_DGRAM,0);
    if(fd < 0)
    {
        perror("socket\n");
        return 2;
    }
    struct sockaddr_in server_addr;
    bzero(&server_addr, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(atoi(argv[2]));
    server_addr.sin_addr.s_addr = inet_addr(argv[1]); 

    int bd = bind(fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
    if(bd < 0)
    {
        perror("bind\n");
        return 3;
    }

    char buf[1024] = {0};
    struct sockaddr_in client_addr;
    while(1)
    {
        socklen_t len = sizeof(client_addr);
        ssize_t s = recvfrom(fd,buf,sizeof(buf)-1,0,(struct sockaddr*)&client_addr,&len);
        if(s < 0)
        {
            perror("recvfrom");
            continue;
        }
        buf[s] = '\n';
        printf("[%s]: [%d]: %s",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port),buf);

        sendto(fd,buf,strlen(buf),0,(struct sockaddr*)&client_addr,sizeof(client_addr));
    }
    close(fd);
    return 0;
}
/////////////////////////////////////////////////////////////////////////////
client.c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>

//   ./client 127.0.0.1 9090
int main(int argc,const char* argv[])
{
    int fd = socket(AF_INET,SOCK_DGRAM,0);
    if(fd < 0)
    {
        perror("socket\n");
        return 1;
    }

    struct sockaddr_in server;
     bzero(&server, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(atoi(argv[2]));
    server.sin_addr.s_addr = inet_addr(argv[1]);

    char buf[1024] = {0};
    struct sockaddr_in peer;
    
    while(1)
    {
        socklen_t len = sizeof(peer);
        printf(">>>");
        fflush(stdout);
        ssize_t s = read(0,buf,sizeof(buf)-1);
        if(s < 0)
        {
            perror("read\n");
            return 1;
        }
        buf[s-1] = '\n';
        sendto(fd,buf,strlen(buf),0,(struct sockaddr*)&server,sizeof(server));
        ssize_t rc = recvfrom(fd,buf,sizeof(buf)-1,0,(struct sockaddr*)&peer,&len);
        if(rc > 0)
        {
            buf[rc] = '\n';
            printf("server echo: %s\n",buf);
        }
    }
    close(fd);
    return 0;
}

TCP套接字编程

主要函数:
1.创建套接字

int socket(int domain,int type,int protocol);
//domain:该参数一般被设置为AF_INET,表示使用的是IPv4地址。还有更多选项可以利用man查看该函数
//type:该参数也有很多选项,例如SOCK_STREAM表示面向流的传输协议,SOCK_DGRAM表示数据报,我们这里实现的是TCP,因此选用SOCK_STREAM,如果实现UDP可选SOCK_DGRAM
//protocol:协议类型,一般使用默认,设置为0

2.绑定

int bind(int sockfd,const struct sockaddr*addr,socklen_t addrlen);
//sockfd:服务器打开的sock
//后两个参数可以参考第四部分的介绍

3.监听

int listen(int sockfd,int backlog);
//sockfd的含义与bind中的相同。
//backlog参数解释为内核为次套接口排队的最大数量,这个大小一般为5~10,不宜太大(是为了防止SYN攻击)

4.接受连接

int accept(int sockfd,struct sockaddr* addr,socklen_t* addrlen);
//addrlen是一个传入传出型参数,传入的是调用者的缓冲区cliaddr的长度,以避免缓冲区溢出问题;传出的是客户端地址结构体的实际长度(有可能没有占满调用者提供的缓冲区)。如果给cliaddr参数传NULL,表示不关心客户端的地址。

典型的服务器程序是可以同时服务多个客户端的,当有客户端发起连接时,服务器就调用accept()返回并接收这个连接,如果有大量客户端发起请求,服务器来不及处理,还没有accept的客户端就处于连接等待状态。
三次握手完成后,服务器调用accept()接收连接,如果服务器调用accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来。
5、请求连接

int connect(int sockfd,const struct sockaddr* addr,socklen_t addrlen);

TCP连接的基本流程:
服务器:首先调用socket()创建一个套接字用来通讯,其次调用bind()进行绑定这个文件描述符,并调用listen()用来监听端口是否有客户端请求来,如果有,就调用accept()进行连接,否则就继续阻塞式等待直到有客户端连接上来。连接建立后就可以进行通信了。

客户端:调用socket()分配一个用来通讯的端口,接着就调用connect()发出SYN请求并处于阻塞等待服务器应答状态,服务器应答一个SYN-ACK分段,客户端收到后从connect()返回,同时应答一个ACK分段,服务器收到后从accept()返回,连接建立成功。客户端一般不调用bind()来绑定一个端口号,并不是不允许bind(),服务器也不是必须要bind()。

为什么不建议客户端进行bind()?
答:当客户端没有自己进行bind时,系统随机分配给客户端一个端口号,并且在分配的时候,操作系统会做到不与现有的端口号发生冲突。但如果自己进行bind,客户端程序就很容易出现问题,假设在一个PC机上开启多个客户端进程,如果是用户自己绑定了端口号,必然会造成端口冲突,影响通信。

TCP通信的一个例子:

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

//      ./server  127.0.0.1 8080
int main(int argc,char* argv[])
{
    if(argc != 3)
    {
        printf("Usage: ./server [ip] [port]\n");
        return 1;
    }

    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(sockfd < 0)
    {
        perror("sock");
        return 1;
    }

    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]);

    int bd = bind(sockfd,(struct sockaddr*)&server,sizeof(server));
    if(bd < 0)
    {
      perror("bind");
      return 1;
    }


    int ls = listen(sockfd,5);
    if(ls < 0)
    {
        perror("listen");
         return 1;
    }

    while(1)
    {
        struct sockaddr_in client;
        socklen_t len = sizeof(client);
      
        
        int  new_fd = accept(sockfd,(struct sockaddr*)&client,&len);
        if(new_fd < 0)
        {
            perror("accept");
            continue;
        }

        while(1)
        {
        char buf[1024] = {0};
        ssize_t read_size = read(new_fd,buf,sizeof(buf)-1);
        if(read_size < 0)
        {
            perror("read");
            continue;
        }
        if(read_size == 0)
        {
            printf("client close!\n");
            break;
        }

        buf[read_size] = '\0';
        printf("[%s:%d]:%s\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port),buf);
        write(new_fd,buf,strlen(buf));
    }
        close(new_fd);
    }
    return 0;
}

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


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

    if(argc != 3)
    {
        printf("Usage: ./client [ip] [port]\n");
        return 1;
    }

    int fd = socket(AF_INET,SOCK_STREAM,0);
        if(fd < 0)
        {
            perror("socket");
            return 1;
        }

    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]);

    int ret = connect(fd,(struct sockaddr*)&server,sizeof(server));
    
      if(ret < 0)
       {
      perror("connect");
       return 1;
      }else
      {
      printf("connect success!\n");
      }


    while(1)
    {
    char buf[1024] = {0};
    printf(">>>");
    fflush(stdout);
    int read_size = read(0,buf,sizeof(buf)-1);
    if(read_size < 0)
    {
        perror("read");
        continue;
    }
    if(read_size == 0)
    {
        printf("goodbye\n");
        break;
    }
    buf[read_size] = '\0';
    write(fd,buf,strlen(buf));
    char buf_output[1024] = {0};
    ssize_t size = read(fd,buf_output,sizeof(buf_output)-1);
    if(size < 0)
    {
        perror("read");
        continue;
    }
    if(size ==0)
    {
        printf("goodbye!");
        break;
    }
    buf_output[size] = '\0';
    printf("%s\n",buf_output);
    }

    close(fd);
    return 0;
}

**一般而言,UDP和TCP编程会使用到的基本函数。**

// 创建socket 文件描述符(TCP/UDP, 客户端+ 服务器)
int socket(int domain, int type, int protocol);
// 绑定端口号(TCP/UDP, 服务器) 
int bind(int socket, const struct sockaddr *address,
socklen_t address_len);
// 开始监听socket (TCP, 服务器)
int listen(int socket, int backlog);
// 接收请求(TCP, 服务器)
int accept(int socket, struct sockaddr* address,
socklen_t* address_len);
// 建立连接(TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
相关标签: socket