Linux TCP套接字编程

Linux TCP套接字编程


TCP套接字编程的基本步骤

服务端编程的步骤如下:

  1. 创建服务端套接字。
  2. 绑定套接字到一个IP地址和一个端口上(使用bind函数)。
  3. 将套接字设置为监听模式等待连接请求(使用函数listen),这个套接字就是监听套接字了。
  4. 请求到来后,接受连接请求。返回一个新的对应此连接的套接字。
  5. 用返回的新的套接字和客户端进行通信,即发送或接收数据(使用send 或者 recv 函数),通信结束就关闭这个新创建的套接字(使用函数 closesocket)。
  6. 监听套接字继续处于监听状态,等待其他客户端的连接请求。
  7. 如果要退出服务器程序,则先关闭监听套接字(使用函数closesocket)。

客户端编程的步骤如下:

  1. 创建客户端套接字(使用函数 socket)。
  2. 向服务器发出连接请求(使用函数 connect)。
  3. 和服务器端进行通信,即发送或接收数据(使用函数 send 或 recv)。
  4. 如果要关闭客户端程序,则先关闭套接字(使用函数closesocket)。

什么是阻塞与非阻塞套接字?

  当使用函数 socket 创建套接字时,默认都是阻塞模式阻塞模式是指套接字在执行操作时,调用函数在没有完成操作之前不会立即返回。也就是说,调用 API 函数时,不能立即完成,线程需要处于等待状态,直到操作完成。但并不是调用所有的API函数都会阻塞,例如 bind(),listen(),即使用阻塞套接字当参数传入,函数也会立即返回,不需要等待。

以下 Linsock API 使用阻塞套接字当参数调用函数时,会发生阻塞。

  1. 接收连接函数

    函数accept从请求连接队列中接收一个客户端连接。若请求队列为空,则函数就会阻塞,线程进入睡眠状态。

  2. 发送函数

    函数send、sendto 都是发送数据函数。如果套接字缓冲区没有可用空间,函数就会阻塞,线程就会睡眠,直到缓冲区有空间。

  3. 接收函数

    函数recv 用来接收数据。如果此时套接字缓冲区没有数据可读,则函数阻塞,调用线程在数据到来前处于睡眠状态。

  4. 连接函数

    函数 connect 用于向对方发送连接请求。发出连接时,直到收到服务器的应答或超时才会返回。

下面就用一个简单的服务器客户机聊天程序(非阻塞套接字版)来进行演示TCP通信。

先创建服务端程序:

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

int main()
{
    int sfp,nfp;
    struct sockaddr_in sAddr,cAddr;
    socklen_t sinSize;
    unsigned short portNum = 10051;
    printf("info: Hello, I am a server, Welcome to connect me!\n");
    sfp = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == sfp)
    {
        printf("error: socket fail!\n");
        return -1;
    }
    printf("info: socket ok!\n");

    int on = 1;
    setsockopt(sfp, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); //  允许地址可立即重用

    bzero(&sAddr, sizeof(struct sockaddr_in));
    sAddr.sin_family = AF_INET;
    sAddr.sin_addr.s_addr = htonl(INADDR_ANY);	//	INADDR_ANY = 0.0.0.0 
    sAddr.sin_port = htons(portNum);	//	htons 主机字节序转换成网络字节序

    if (-1 == bind(sfp, (struct sockaddr *)(&sAddr), sizeof(struct sockaddr)))
    {
        printf("error: bind fail:%d\n", errno);
        return -1;
    }
    printf("info: bind ok!\n");

    if (-1 == listen(sfp, 5))	//	5是最大连接数
    {
        printf("error: listen fail!\n");
        return -1;
    }
    printf("info: listen ok!\n");

    while (1)
    {
        sinSize = sizeof(struct sockaddr_in);
        nfp = accept(sfp, (struct sockaddr *)(&cAddr), &sinSize);
        if (-1 == nfp)
        {
            printf("error: accept fail!\n");
            return -1;
        }
        printf("info: accept ok!\n");
        printf("info: Server start to connect from ip=%s, port=%d\n",
                inet_ntoa(cAddr.sin_addr),
                ntohs(cAddr.sin_port));	//	inet_ntoa 将二进制地址转换成点分十进制地址

        if (-1 == write(nfp, "Hello, client, you are welcome!\n", 32))
        {
            printf("error: write fail!\n");
            return -1;
        }
        printf("info: write ok!\n");
        close(nfp);

        puts("continue to listen(y/n)?");
        char ch[2];
        scanf("%s", ch, 2);
        if (ch[0] != 'y')
            break;
    }

    printf("info: bye!\n");
    close(sfp);
    return 0;
}

服务端先新建一个监听套接字,然后等待客户端的连接请求,阻塞在accept函数处,一旦有客户端连接请求来了,就返回一个新的套接字。这个套接字就和客户端进行通信,通信完毕后关掉这个套接字。而监听套接字根据用户的输入继续监听或退出。

客户端程序:

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <assert.h>
#include <stdio.h>
#include <time.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include "unistd.h"
#include <string.h>

const int BUFFER_SIZE = 1023;

//	将套接字设置为非阻塞
int SetNonBlocking(int fd)
{
    int oldOption = fcntl(fd, F_GETFL);
    int newOption = oldOption | O_NONBLOCK;
    fcntl(fd, F_SETFL, newOption);
    return oldOption;
}

//	非阻塞连接,time是超时时间。
int UnblockConnect(const char* ip, int port, int time)
{
	//	初始化套接字地址
    int ret = 0;
    struct sockaddr_in Address;
    bzero(&Address, sizeof(struct sockaddr_in));
    Address.sin_family = AF_INET;
    inet_pton(AF_INET, ip, &Address.sin_addr);
    Address.sin_port = htons(port);

	//	创建非阻塞套接字,用于客户端连接
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    int fdopt = SetNonBlocking(sockfd);
    ret = connect(sockfd, (struct sockaddr *)(&Address), sizeof(struct sockaddr));
    printf("info: ret code = %d\n", ret);
    if (0 == ret)	//	创建非阻塞套接字,用于客户端连接
    {
        printf("info: connect with server immediately\n");
        fcntl(sockfd, F_SETFL, fdopt);
        return sockfd;
    }
    else if (EINPROGRESS != errno)	//	连接失败
    {
        printf("error: unblock connect failed!\n");
        return -1;
    }
    else if (EINPROGRESS == errno)	//	连接中
    {
        printf("info: unblock mode socket is connecting ...\n");
    }

	//	调用select函数判断连接是否成功
	//	如果套接字是 writable,那么套接字连接成功
    fd_set readfds;
    fd_set writefds;
    struct timeval timeout;
    FD_ZERO(&readfds);
    FD_SET(sockfd, &writefds);

	//	设置超时时间
    timeout.tv_sec = time;
    timeout.tv_usec = 0;

    ret = select(sockfd + 1, NULL, &writefds, NULL, &timeout);
    if (ret < 0)
    {
        printf("error: connection time out\n");
        close(sockfd);
        return -1;
    }

    if (!FD_ISSET(sockfd, &writefds))
    {
        printf("info: no events on sockfd found\n");
        close(sockfd);
        return -1;
    }

	//	如果套接字描述符可读或可写,则我们用getsockopt来得到套接字上待处理的错误
	//	如果连接建立成功,这个错误值将是0。
    int error = 0;
    socklen_t length = sizeof(error);
    if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &length) < 0)
    {
        printf("error: get sock option failed\n");
        close(sockfd);
        return -1;
    }
    if (0 != error)
    {
        printf("error: connection failed after select with the error:%d\n", error);
        close(sockfd);
        return -1;
    }

    printf("info: Connection ready after  select with the socket:%d\n",sockfd);
    fcntl(sockfd, F_SETFL, fdopt);	//	将套接字恢复成原来属性
    printf("info: Connect ok!\n");

    int recvBytes;
    int sinSize;
    char buf[1024] = {0};
    recvBytes = read(sockfd, buf, 1024);	//	接收数据
    if (-1 == recvBytes)
    {
        printf("error: read data fail!\n");
        return -1;
    }
    printf("info: read ok!\n");

    buf[recvBytes] = '\0';
    printf("%s\n", buf);

    return sockfd;
}

int main()
{
    int sockfd = UnblockConnect("127.0.0.1", 10051, 1);
    if (0 > sockfd)
    {
        printf("error: create socket fail!\n");
        return -1;
    }
    close(sockfd);
    return 0;
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值