简单单线程TCP Socket模型

开头

本篇文章为基础的socket编程模型,单线程单连接收发

1 函数解析

  1. socket
/* Create a new socket of type TYPE in domain DOMAIN, using
   protocol PROTOCOL.  If PROTOCOL is zero, one is chosen automatically.
   Returns a file descriptor for the new socket, or -1 for errors.  */
extern int socket (int __domain, int __type, int __protocol)

参数:
__domain:协议族,可设置为:

  • AF_INET IPv4协议 ·

  • AF_INET6 IPv6协议 ·

  • AF_LOCAL Unix域协议 ·

  • AF_ROUTE 路由套接口 ·

  • AF_KEY 密钥套接口

__type:指定套接口类型:

  • SOCK_STREAM 字节流套接口
  • SOCK_DGRAM 数据报套接口
  • SOCK_RAW 原始套接口
    _protocol:一般设为0,除非用在原始套接口上。
    可选有:
enum
{
IPPROTO_IP = 0,	   /* Dummy protocol for TCP.  */
IPPROTO_ICMP = 1,	   /* Internet Control Message Protocol.  */
IPPROTO_IGMP = 2,	   /* Internet Group Management Protocol. */
IPPROTO_IPIP = 4,	   /* IPIP tunnels (older KA9Q tunnels use 94).  */
IPPROTO_TCP = 6,	   /* Transmission Control Protocol.  */
IPPROTO_UDP = 17,	   /* User Datagram Protocol.  */
IPPROTO_IPV6 = 41,     /* IPv6 header.  */
}

并非所有family和type的组合都是有效的。
返回:非负描述字—成功,-1—出错。

  1. bind
/* Give the socket FD the local address ADDR (which is LEN bytes long).  */
extern int bind (int __fd, const struct sockaddr* __addr, socklen_t __len)

功能:进程可以将一个IP和端口绑定到当前的socket套接字上
参数:
__fd:已生成的当前套接字;
__addr:绑定的IP端口结构体;struct sockaddr可使用struct sockaddr_in进行强转;

struct sockaddr_in
  {
    __SOCKADDR_COMMON (sin_);
    in_port_t sin_port;			/* Port number.  */
    struct in_addr sin_addr;		/* Internet address.  */

    /* Pad to size of `struct sockaddr'.  */
    unsigned char sin_zero[sizeof (struct sockaddr) -
			   __SOCKADDR_COMMON_SIZE -
			   sizeof (in_port_t) -
			   sizeof (struct in_addr)];
  };
typedef uint32_t in_addr_t;
struct in_addr
  {
    in_addr_t s_addr;
  };

参数

  • sin_addr 为一个32位整形的IP地址,可使用inet_addr将字符串地址转为int32;
  • __len:结构体的大小
    返回:0—成功,-1—出错。
  1. listen
/* Prepare to accept connections on socket FD.
   N connection requests will be queued before further requests are refused.
   Returns 0 on success, -1 for errors.  */
extern int listen (int __fd, int __n) __THROW;

功能:把未连接的套接口转化为被动套接口,指示内核应接受指向此套接口的连接请求。第二个参数规定了内核为此套接口排队的最大连接数。
参数:

  • __fd:侦听的套接字;
  • __n:最大接收连接请求数;
    返回:0—成功,-1—出错。

4)access

/* Await a connection on socket FD.
   When a connection arrives, open a new socket to communicate with it,
   set *ADDR (which is *ADDR_LEN bytes long) to the address of the connecting
   peer and *ADDR_LEN to the address's actual length, and return the
   new socket's descriptor, or -1 for errors.

   This function is a cancellation point and therefore not marked with
   __THROW.  */
extern int accept (int __fd, __SOCKADDR_ARG __addr,
		   socklen_t *__restrict __addr_len);

功能:从已完成连接队列头返回下一个连接,若已完成连接队列为空,则进程睡眠(套接口为阻塞方式时)
参数:

  • __fd:当前套接字;
  • __addr:连接对端的协议地址,用法同bind一致,使用 struct sockaddr_in 类型强转
  • __addr_len:值-结果参数,调用前addrlen所指的整数值要置为cliaddr所指的套接口结构的长度,返回时由内核修改
    如果对客户的协议地址没有兴趣,可以把cliaddr和addrlen置为空指针。
    返回值:非负描述字—OK,-1—出错。
  1. recv
/* Read N bytes into BUF from socket FD.
   Returns the number read or -1 for errors.

   This function is a cancellation point and therefore not marked with
   __THROW.  */
extern ssize_t recv (int __fd, void *__buf, size_t __n, int __flags);

功能:从套接字fd的缓冲区中拷贝N个字节数据到buf中
参数:

  • __buf:指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据
  • __n:指明缓冲区的大小
  • __flags:参数一般为0,可设置为如下:
MSG_OOB		= 0x01,	/* Process out-of-band data.  */带外数据
MSG_PEEK		= 0x02,	/* Peek at incoming messages.  */
MSG_DONTROUTE	= 0x04,	/* Don't use local routing.  */
MSG_DONTWAIT	= 0x40, /* Nonblocking IO.  */ 非阻塞

返回:实际copy的字节数。如果recv在copy时出错,那么它返回-1;如果recv函数在等待协议接收数据时网络中断了,那么它返回0。
注意:
(1)recv先等待s的发送缓冲中的数据被协议传送完毕,如果协议在传送s的发送缓冲中的数据时出现网络错误,那么recv函数返回SOCKET_ERROR,
(2)如果s的发送缓冲中没有数据或者数据被协议成功发送完毕后,recv先检查套接字s的接收缓冲区,如果s接收缓冲区中没有数据或者协议正在接收数据,那么recv就一直等待,直到协议把数据接收完毕。当协议把数据接收完毕,recv函数就把s的接收缓冲中的数据copy到buf中(注意协议接收到的数据可能大于buf的长度,所以在这种情况下要调用几次recv函数才能把s的接收缓冲中的数据copy完。recv函数仅仅是copy数据,真正的接收数据是协议来完成的) [1] 。
recv函数返回其实际copy的字节数。如果recv在copy时出错,那么它返回SOCKET_ERROR;如果recv函数在等待协议接收数据时网络中断了,那么它返回0。

  1. send
/* Send N bytes of BUF to socket FD.  Returns the number sent or -1.

   This function is a cancellation point and therefore not marked with
   __THROW.  */
extern ssize_t send (int __fd, const void *__buf, size_t __n, int __flags);

功能:向一个已经连接的socket发送数据,如果无错误,返回值为所发送数据的总数,否则返回SOCKET_ERROR。
参数:

  • __fd:指定发送端套接字描述符;
  • __buf:指明一个存放应用程序要发送数据的缓冲区;
  • __n:指明实际要发送的数据的字节数;
  • __flags:第四个参数一般置0,此时同write()函数
    其他可用flag参数:
MSG_OOB

返回:成功返回拷贝长度;失败返回-1
注意:

  1. 并非是直接发送;而是将buf拷贝到套接字的发送缓冲区中,所以,会先比较待发送长度和缓冲区大小;如果发送长度大于缓冲区长度;则返回SOCKET_ERROR;如果小于,则检查当前协议是否正在发送缓冲区中数据;如果正在发送,就等待协议把数据发送完,如果还没开始,或者缓冲区中没有数据,就比较缓冲区的剩余空间和发送长度;如果len大于剩余空间,就一直等待协议把缓冲区中数据发送完,如果小于剩余空间,send就仅仅把buf中的数据copy到剩余空间中;

  2. 如果send函数copy数据成功,就返回实际copy的字节数,如果send在copy数据时出现错误,那么send就返回SOCKET_ERROR;如果send在等待协议传送数据时网络断开的话,那么send函数也返回SOCKET_ERROR。

  3. 要注意send函数把buf中的数据成功copy到s的发送缓冲的剩余空间里后它就返回了,但是此时这些数据并不一定马上被传到连接的另一端。如果协议在后续的传送过程中出现网络错误的话,那么下一个Socket函数就会返回SOCKET_ERROR。(每一个除send外的Socket函数在执行的最开始总要先等待套接字的发送缓冲中的数据被协议传送完毕才能继续,如果在等待时出现网络错误,那么该Socket函数就返回SOCKET_ERROR)。

  4. close

/* Close the file descriptor FD.

   This function is a cancellation point and therefore not marked with
   __THROW.  */
extern int close (int __fd);

关闭文件描述符

  1. connect
/* Open a connection on socket FD to peer at ADDR (which LEN bytes long).
   For connectionless socket types, just set the default address to send to
   and the only address from which to accept transmissions.
   Return 0 on success, -1 for errors.

   This function is a cancellation point and therefore not marked with
   __THROW.  */
extern int connect (int __fd, __CONST_SOCKADDR_ARG __addr, socklen_t __len);

功能:用__fd去连接__addr 对端;对于无连接socket类型,设置默认对端地址以发送;
参数:同上
返回:成功返回0 ,失败返回 -1

2、地址转换接口

int inet_aton(const char *strptr, struct in_addr*addrptr);

返回:1—串有效,0—串有错。

in_addr_t inet_addr(const char *strptr);

返回:若成功,返回32为二进制的网络字节序地址;若有错,则返回INADDR_NONE。

char *inet_ntoa(struct in_addr inaddr);

返回:指向点分十进制数串的指针。

注意:

  • inet_aton函数的指针若为空,则函数仍然执行输入串的有效性检查,但不存储任何结果。
  • inet_addr的缺陷:出错返回值INADDR_NONE等于255.255.255.255(IPv4的有限广播地址),所以该函数不能处理此地址。
    尽量使用inet_aton,不使用inet_addr。
  • inet_ntoa函数的执行结果放在静态内存中,是不可重入的。

3 、示例:

server.c

/**
author:locki
description:simple tcp server
**/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

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

// 服务端测试
int main()
{
    int ret = 0;
    
    int fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    struct sockaddr_in srvaddr;
    struct sockaddr_in cli = { 0 };
    socklen_t len = sizeof(cli);

        //srvaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    inet_aton("127.0.0.1", &srvaddr);
    //srvaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    srvaddr.sin_family = AF_INET;
    srvaddr.sin_port = htons(8888);
    if (-1 == (ret = bind(fd, (struct sockaddr*)&srvaddr, sizeof(srvaddr)))) 
    {
        printf("bind 127.0.0.1:8888 failed errno:%d\n", errno);
        goto SOCKEXIT;
    }

    if (-1 == (ret = listen(fd, 10))) {
        printf("listen 10 failed;errno:%d\n", errno);
        goto SOCKEXIT;
    }

    int clifd = 0;
    if (-1 == (clifd = accept(fd, (struct sockaddr*)&cli, &len))) {
        printf("accept failed:%d\n", errno);
        goto SOCKEXIT;
    }
    printf("recv new connect; peer ip = %s port = %d; fd = %d\n",\
     inet_ntoa(cli.sin_addr), cli.sin_port,  clifd);
    while (1){
        char buf[1024] = { 0 };
        if (-1 == recv(clifd, buf, 1024, 0)) {
            printf("recv failed:%d\n", errno);
            goto SOCKEXIT;
        }else {
            char sendbuf[1024] = { 0 };
            printf("recv:%s\n", buf);
            sprintf(sendbuf, "send:%s", buf);
            if (-1 == send(clifd, sendbuf, strlen(sendbuf), 0)) {
                printf("send:%s failed %d\n", sendbuf, errno);
                continue;
            }else {
                printf("send:%s success\n", sendbuf);
            }
        }
    }

SOCKEXIT:
    close(fd);
    return ret;
}

client.c

/**
author:locki
description:simple tcp client
**/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include<errno.h>

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

// 客户端测试
int main()
{
    int ret = 0;
    int fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (fd == -1) {
        printf("create socket failed:errno:%d", errno);
        return -1;
    }

    struct sockaddr_in cliaddr = { 0 };
    cliaddr.sin_addr.s_addr = inet_addr("192.168.0.195");
    cliaddr.sin_family = AF_INET;
    cliaddr.sin_port = htons(8888);
    if (-1 == (ret = connect(fd, (struct sockaddr*)&cliaddr, sizeof(cliaddr))))
    {
        printf("connect to 127.0.0.1:8888 failed; errno:%s\n");
        return -1;
    }
    printf("connect to server 127.0.0.1:8888 sunccessed\n");

    char buf[32] = { 0 };
    while (1)
    {
        sprintf(buf, "%d", time(NULL));
        if (-1 == (ret = send(fd, buf, sizeof(buf), 0))) {
            printf("send to server:%s failed; errno:%d\n", buf, errno);
        }else {
            printf("send to server:%s success;\n");
        }
        sleep(1);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值