Socket套接字编程

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

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

UDP通信的流程比较简单,因此要搭建这么一个常用的UDP通信框架也是比较简单的。以下是UDP的框架图。
在这里插入图片描述

由以上框图可以看出,客户端要发起一次请求,仅仅需要个步骤(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);
  • 12
    点赞
  • 78
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值