TCP/IP笔记二(套接字通讯)

  • Post author:
  • Post category:其他

一、TCP连接通讯示意图

 

二、TCP/UDP通讯使用到的套接字

TCP:流式套接字,面向连接,保证传输的数据是顺序的正确的。
UDP:数据报套接字,面向无连接,数据通过相互独立的报文进行传播,是无序的,并且不保证可靠。

套接字工作过程如下:服务器首先启动,通过调用socket()建立一个套接字,然后调用bind()将该套接字和本地网络地址联系在一起,再调用listen()使套接字做好帧听的准备,并规定它的请求队列的长度,之后调用accept()来接收连接。客户端在建立好套接字后就可调用connect()和服务器建立连接。连接一旦建立,客户机和服务器之间就可以通过调用read()和write()来发送和接收数据。最后,待数据传送结束后,双方调用close关闭套接字。

1、int socket(int domain, int type, int protocol);
作用:用于创建网络连接套接字。
参数:
   domain:用于设置网络通讯的域,函数根据这个参数选择通讯协议的族,通常为AF_INET,表示ipv4;
   type:  用于设置套接字通讯的类型,主要有SOCK_STREAM(流式套接字,支持TCP)和SOCK_DGRAM(数据包报文,支持UDP
   protocol: 用于指定协议的特定类型,通常某协议只有一种特定的类型,该参数设置为0。

返回值:
    成功,返回套接字描述符;失败,返回-1。

2、int bind(int sockfd, struct sockaddr *my_addr, int addrlen);
作用:用来指定一个套接字使用的端口。
参数:sockfd 是由socket()函数返回的套接字描述符。
           my_addr 是一个指向struct sockaddr的指针,包含地址信息:名称、端口和IP地址。
           addrlen 可以设置为sizeof(struct sockaddr)。
返回值:成功返回0,出错返回-1。

3、int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
作用:用于连接服务器。
参数:
           sockfd:socket()创建成功之后返回的网络描述符。
           addr:需要连接的服务器的IP地址/端口号/网络类型。
           addrlen:addr长度。
返回值:成功,返回0;失败,返回-1。
    
4、int listen(int sockfd, int backlog);
作用:等待别人连接,进行系统侦听请求的函数。
参数:
          sockfd是一个套接字描述符,由socket()系统获得。
          backlog是未经过处理的连接请求队列可以容纳的最大数目。
返回值:成功,返回0;失败,返回-1。
补充:每一个连入请求都要进入一个连入请求队列,等待listen的程序调用accept(),函数来接收这个连接。当系统还没有调用accept()函数的时候,若有多个连接,那么本地能够等待的最大数目就是backlog的数值,通常设置为5到10之间。

5、int accept(int sockfd, void *addr, int *addrlen);
作用:当前连接处于listen状态,用来接受远程计算机的连接。
参数:sockfd 是正在listen()的一个套接字描述符。
           addr 指向struct sockaddr_in结构的指针;里面存储着远程连接过来的计算机信息。
           addrlen sizeof(struct sockaddr_in)。
返回值:成功,返回0;失败,返回-1。

    ssize_t send(int sockfd, const void *buf, size_t len, int flags);
作用:通过连接的套接字流进行通讯的函数。
参数:sockfd 是代表与远程程序连接的套接字描述符。
           msg 是一个指针,指向想要发送的信息的地址。
           len 发送信息的长度。
           flag 发送标记,一般设置为0。

返回值:返回发送数据的长度,如果发送数据的长度大于发送的最大长度,则实际发送长度为最大发送长度。

    ssize_t recv(int sockfd, void *buf, int len, unsigned int flags);
作用:通过连接的套接字流进行通讯的函数。
参数:sockfd 是代表与远程程序连接的套接字描述符。
           buf  是一个指针,指向想要存储数据的内存缓存区域。
           len  缓存区的最大尺寸。
           flag 发送标记,一般设置为0。
返回值:返回它所真正收到的数据的长度,-1代表发生了错误。

    int sendto(int sockfd, const void *msg, size_t len, unsigned int flags,const struct sockaddr *to, int tolen);
    int recvfrom(int sockfd, void *buf, size_t len, unsigned int flags, struct sockaddr *from, int *fromlen);
作用:这两个函数是进行无连接的UDP通讯时使用,通过这两个函数,数据会在没有建立任何连接的网络上传输。
参数:与send和rece类似。

6、int close(int fd);
作用:关闭一个连接。
参数:
      fd,需要关闭的连接的套接字描述符。

三、客户端/服务端通讯示例

client.c

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <string.h>

#define SERVER_ADDR  “192.168.1.10”
#define SERVER_PORT  9003
#define CMD_REGISTER    1001  //注册学生信息
#define CMD_CHECK       1002  //检验学生信息
#define CMD_GETINFO     1003  //获取学生信息
#define STAT_OK         30    //回复OK
#define STAT_ERR        31    //回复出错

char sendbuf[100];
char recvbuf[100];
typedef struct commu
{
    char name[20];
    int age;
    int cmd;
    int stat;
}info;

int main(void)
{
  //第一步,先sock打开文件描述符
  int sockfd = -1,ret=-1;
  struct sockaddr_in seraddr={0};
  struct sockaddr_in cliaddr={0};

  //第一步:socket
  sockfd=socket(AF_INET, SOCK_STREAM, 0);
  if(-1 == sockfd)
  {
       perror(“socket”);
       return -1;
  }

  //第二步,connect链接服务器
  seraddr.sin_family = AF_INET;               //设置地址族为IPv4
  seraddr.sin_addr.s_addr = inet_addr(SERVER_ADDR);  //设置IP地址
  seraddr.sin_port = htons(SERVER_PORT);            //设置地址的端口号信息
  ret=connect(sockfd,(const struct sockaddr *)&seraddr,sizeof(seraddr));
  if(ret<0)
  {
       perror(“listen”);
       return -1;
  } 
  printf(“成功建立连接”);

  while (1)
  {
    // 回合中第1步:客户端给服务器发送信息
    info st1;
    printf(“请输入学生姓名\n”);
    scanf(“%s”, st1.name);
    printf(“请输入学生年龄”);
    scanf(“%d”, &st1.age);
    st1.cmd = CMD_REGISTER;
    //printf(“刚才输入的是:%s\n”, sendbuf);
    ret = send(sockfd, &st1, sizeof(info), 0);
    printf(“发送了1个学生信息\n”);
        
    // 回合中第2步:客户端接收服务器的回复
    memset(&st1, 0, sizeof(st1));
    ret = recv(sockfd, &st1, sizeof(st1), 0);
    
    // 回合中第3步:客户端解析服务器的回复,再做下一步定夺

    return 0;
}

serive.c

#define SERPORT        9003
#define SERADDR        “192.168.1.141”        // ifconfig看到的
#define BACKLOG        100
#define CMD_REGISTER    1001    // 注册学生信息
#define CMD_CHECK        1002    // 检验学生信息
#define CMD_GETINFO        1003    // 获取学生信息
#define STAT_OK            30        // 回复ok
#define STAT_ERR        31        // 回复出错了

char recvbuf[100];
typedef struct commu
{
    char name[20];        // 学生姓名
    int age;            // 学生年龄
    int cmd;            // 命令码
    int stat;            // 状态信息,用来回复
}info;

int main(void)
{
    // 第1步:先socket打开文件描述符
    int sockfd = -1, ret = -1, clifd = -1;
    socklen_t len = 0;
    struct sockaddr_in seraddr = {0};
    struct sockaddr_in cliaddr = {0};
    char ipbuf[30] = {0};
    
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == sockfd)
    {
        perror(“socket”);
        return -1;
    }
    printf(“socketfd = %d.\n”, sockfd);
    
    // 第2步:bind绑定sockefd和当前电脑的ip地址&端口号
    seraddr.sin_family = AF_INET;        // 设置地址族为IPv4
    seraddr.sin_port = htons(SERseraseraddrPORT);    // 设置地址的端口号信息
    seraddr.sin_addr.s_addr = inet_addr(SERADDR);    // 设置IP地址
    ret = bind(sockfd, (const struct sockaddr *)&seraddr, sizeof(seraddr));
    if (ret < 0)
    {
        perror(“bind”);
        return -1;
    }
    printf(“bind success.\n”);
    
    // 第三步:listen监听端口
    ret = listen(sockfd, BACKLOG);        // 阻塞等待客户端来连接服务器
    if (ret < 0)
    {
        perror(“listen”);
        return -1;
    }
    
    // 第四步:accept阻塞等待客户端接入
    clifd = accept(sockfd, (struct sockaddr *)&cliaddr, &len);
    printf(“连接已经建立,client fd = %d.\n”, clifd);
    
    // 客户端反复给服务器发
    while (1)
    {
        info st;
        // 回合中第1步:服务器收
        ret = recv(clifd, &st, sizeof(info), 0);

        // 回合中第2步:服务器解析客户端数据包,然后干活,
        if (st.cmd == CMD_REGISTER)
        {
            printf(“用户要注册学生信息\n”);
            printf(“学生姓名:%s,学生年龄:%d\n”, st.name, st.age);
            // 在这里服务器要进行真正的注册动作,一般是插入数据库一条信息
            
            // 回合中第3步:回复客户端
            st.stat = STAT_OK;
            ret = send(clifd, &st, sizeof(info), 0);
        }
    }
    return 0;
}

 


版权声明:本文为qq_35546186原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。