精华内容
下载资源
问答
  • Linux Socket

    千次阅读 2018-07-11 14:27:49
    Socket创建内核参照2.6.34 (部分2.6.21)函数调用sys_socketall() => sys_socket() =>sock_create() => __sock_create()概要流程1. 分配socket结构空间2. 记录socket类型3. 检查、取得协议族...

    Socket创建


    内核参照2.6.34 (部分2.6.21)

    函数调用

    sys_socketall() => sys_socket() =>sock_create() => __sock_create()


    概要流程


    1.      分配socket结构空间

    2.      记录socket类型

    3.      检查、取得协议族操作表

     

    注:

    Kernel 通过initcall将inet_family注册到net_families,如下:

    fs_initcall(inet_init); => (void)sock_register(&inet_family_ops);

     

     

     

    在inet中,会在inet_init()注册不同的sockettype: SOCK_STREAM, SOCK_DGRAM, SOCK_RAW. (这里注册的是不同类型的socket对应的操作所需要的处理函数.也就是各中socket的bind(), listen(), accept()等操作的处理函数.)

     

     

     



     

     


     

    4.      执行协议族的创建函数

    5.      利用协议族的函数表初始化socket


    监听连接listen

    调用协议族相应的处理函数,inet则调用inet_listen();


    接收连接accept


    调用协议族相应的处理函数,inet则调用inet_accept();


    准备连接请求connect

    1.        内核的连接

    sys_socketcall() => sys_connect() =>inet_stream_connect() => tcp_v4_connect()

    1)        端口检查与复用

    2)        MTU,MSS的设置

    3)        滑动窗口的初始化

    4)        …

     

    Tcp_connect() 发送第一次握手的SYN数据包

    2.        分配数据包结构和数据空间

    tcp_v4_connect() => tcp_connect() => alloc_skb_fclone() =>__alloc_skb()

     

    3.        构建、发送TCP数据包

    Tcp_connect() => tcp_transmit_skb()

    4.        进化成IP数据包

    Tcp_connect() => tcp_transmit_skb() => ip_queue_xmit()

    Tcp_connect() => tcp_transmit_skb() => ip_queue_xmit() =>ip_local_out => dst_output() => ip_output() => ip_finish_output()=> ip_finish_output2() => neigh_resolve_output()

     

    5.        进化成以太网数据包

    neigh_resolve_output() => neigh_hh_init()

     

    6.        发送以太网数据包

    neith->ops->queue_xmit();

    arp_hh_ops中queue_xmit() 对应dev_queue_xmit();

     

    neigh_resolve_output() => dev_queue_xmit();


    建立连接的过程


    接收数据包

    注册网卡设备驱动函数

    以cs89x0为例, 网卡设备驱动函数调用如下:

    init_module() => cs89x0_probe1() => dev->netdev_ops= &net_ops; => register_netdev(dev);

     

    1.        当使用ifconfig eth0 up启动网卡时会执行系统调用sys_ioctl(), 最终内核会执行dev_open();

     

    dev_open() =>__dev_open();

     

    如下,__dev_open()中最终会调用net_ops中的net_open();

     

     

    2.        在net_open()中会注册中断函数net_ineterrupt(), 如下:

     

    3.        中断到来时会调用net_interrupt(), 如果是由于接收数据包产生的中断, 则调用net_rx();

     

    4.        在net_rx()中

    a)        分配数据包结构和缓冲块

    b)       将数据读入到数据包的数据块中,并调用skb_put调整数据块

    c)        记录协议

    d)       调用netif_rx()通知内核接收数据包

    5.        netif_rx()处理数据包, 使用NAPI,采用轮询的方式获取数据包,改善网络处理的性能.

     

    这里的backlog在net_dev_init()中初始化, 如下:

    l  初始化接收数据包队列和NAPI队列

    l  设置数据包处理函数process_backlog();

    l  注册发送数据包的软中断函数net_tx_action();

    l  注册接收数据包的软中断函数net_rx_action();

     


     

    6.        在net_rx_action()中, 如下, poll()会调用上面注册的函数process_backlog()处理数据包.

    do_softirq() => net_rx_action() => process_backlog()

     

    7.        在process_backlog()中调用netif_receive_skb()向上层传递数据包.

    8.        在netif_receive_skb()中查看是否是bridge, vlan, 是则进入相应的处理, 不是则根据数据包的协议类型, 调用deliver_skb()传递数据包.

    此处的pt_prev->func() 由如下的实现在inet_init()中注册.

     


     


    对于ip 数据包, packet_type在inet_init()函数中注册, 如下:

     



    IP处理数据包

     

    1.        调用ip_rcv(), 进入IP数据包的处理流程, 同时进入netfilter系统.

    a)        Ip_rcv()首先解析IP数据包,如包头, checksum等;

    b)       调用NF_HOOK, 进入NF_INET_PRE_ROUTING处理, 处理结束后调用ip_rcv_finish();

    c)        ip_rcv_finish()主要处理两件事情:

                            i.             明确数据包是本地接收还是转发, 如果是转发就需要进一步明确转发的网络设备和跳转出口.

                          ii.             分析和处理关于IP选项的内容

    d)       接下来通过dst_input()调用路由表的输入处理函数:

                            i.             如果是转发的数据包, 转发路由表指定的输入处理函数为ip_forward(), 这个函数最终调用dst_output函数转发数据包.

    Ip_rcv_finish()=> ip_route_input() => ip_route_input_slow() => ip_mkroute_input()=> __mkroute_input()

     

                                          i.             Ip_forward() 进入netfilter的后续处理

     


     

     

    ip_finish_output()=> ip_finish_output2() => net_ratelimit();

     

                          ii.             如果是本地的数据包, 本地路由表指定的输入处理函数为ip_local_deliver()这个函数继续处理数据包.

     

               Ip_rcv_finish() => ip_route_input() => ip_route_input_slow()

     

                                          i.             对数据包IP头部进行检查, 查看是否是分段数据包, 如果是则调用ip_defrage()将分段数据包插入分段队列; 如果是接收的最后一个分段数据包, 则重组数据包.

                                        ii.             无论是重组后的数据包还是直接接收的数据包都要进入ip_local_deliver_finish()函数交给传输层处理.


    TCP数据包处理及三次握手


    1.        根据IP头部获取当前数据包的协议类型;

    2.        传递给raw socket处理

    3.        获取传输层函数表的数据结构, 调用传输层函数表的处理函数

     

     

     

    a)        传输层函数表是由inet_init()注册. (此处内核注册了对不同协议类型的数据包的处理)

     


     

    b)       对于tcp, 即调用tcp_v4_rcv();

     

    4.        Tcp_v4_rcv()及三次握手



    发送与接收数据包

    发送和接收数据包的函数有: 文件系统APIs: read(), write(), SOCKET APIs: send(), recv(), sendto(),recvfrom(), sendmsg(), recvmsg(). 经分析, 无论是文件系统APIs, 还是socket APIs最终都会调用sock_sendmsg()/__sock_sendmsg(),sock_recvmsg()/__sock_recvmsg().

     

    针对不同类型的数据类型的socket, 内核注册了不同的针对socket操作的处理函数, 见概要流程 #3.



    发送数据包






    接收数据包













    展开全文
  • linux socket

    千次阅读 2012-07-13 17:05:53
    1、socket()函数 int socket(int domain, int type, int protocol); 参见/usr/include/bits/socket.h socket函数对应于普通文件的打开操作。普通文件的打开操作返回一个文件描述字,而socket()用于创建...

    1、socket()函数

    int socket(int domain, int type, int protocol);

    参见/usr/include/bits/socket.h

    socket函数对应于普通文件的打开操作。普通文件的打开操作返回一个文件描述字,而socket()用于创建一个socket描述符(socket descriptor),它唯一标识一个socket。这个socket描述字跟文件描述字一样,后续的操作都有用到它,把它作为参数,通过它来进行一些读写操作。正如可以给fopen的传入不同参数值,以打开不同的文件。创建socket的时候,也可以指定不同的参数创建不同的socket描述符,socket函数的三个参数分别为:

    • domain:即协议域,又称为协议族(family),地址族。常用的协议族有,AF_INETAF_INET6AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。
    /* Address families.  */
    #define AF_UNSPEC   PF_UNSPEC      ->Unspecified. 
    #define AF_LOCAL    PF_LOCAL             ->Local to host (pipes and file-domain)    UNIX 进程通信协议。
    #define AF_UNIX     PF_UNIX                 ->Old BSD name for PF_LOCAL.
    #define AF_FILE     PF_FILE                     ->Another non-standard name for PF_LOCAL.
    #define AF_INET     PF_INET                  ->IP protocol family.   Ipv4网络协议。
    #define AF_AX25     PF_AX25                  -> Amateur Radio AX.25.   业余无线AX.25协议。
    #define AF_IPX      PF_IPX                        ->  Novell Internet Protocol.  IPX-Novell协议
    #define AF_APPLETALK    PF_APPLETALK        -> Appletalk DDP.   appletalkDDP)协议。
    #define AF_NETROM   PF_NETROM                      ->Amateur radio NetROM.
    #define AF_BRIDGE   PF_BRIDGE                            ->Multiprotocol bridge. 
    #define AF_ATMPVC   PF_ATMPVC                       -> ATM PVCs.  存取原始ATM PVCs
    #define AF_X25      PF_X25                                           ->Reserved for X.25 project.  ITU-T X.25/ISO-8208 协议。
    #define AF_INET6    PF_INET6                                 ->IP version 6.  Ipv6 网络协议。
    #define AF_ROSE     PF_ROSE                                     ->Amateur Radio X.25 PLP.
    #define AF_DECnet   PF_DECnet                               ->Reserved for DECnet project.
    #define AF_NETBEUI  PF_NETBEUI                     ->Reserved for 802.2LLC project.
    #define AF_SECURITY PF_SECURITY                ->Security callback pseudo AF.
    #define AF_KEY      PF_KEY                                        ->PF_KEY key management API.
    #define AF_NETLINK  PF_NETLINK                     核心用户接口装置。
    #define AF_ROUTE    PF_ROUTE                            -> Alias to emulate 4.4BSD.
    #define AF_PACKET   PF_PACKET                        ->Packet family.    初级封包接口
    #define AF_ASH      PF_ASH                                        ->Ash. 
    #define AF_ECONET   PF_ECONET                       ->Acorn Econet.
    #define AF_ATMSVC   PF_ATMSVC                     ->ATM SVCs.
    #define AF_SNA      PF_SNA                                       ->Linux SNA Project
    #define AF_IRDA     PF_IRDA                                   ->IRDA sockets.
    #define AF_PPPOX    PF_PPPOX                            ->PPPoX sockets.
    #define AF_WANPIPE  PF_WANPIPE                ->Wanpipe API sockets.
    #define AF_BLUETOOTH    PF_BLUETOOTH     ->Bluetooth sockets.
    #define AF_MAX      PF_MAX             
    • type:指定socket类型。常用的socket类型有,SOCK_STREAMSOCK_DGRAMSOCK_RAWSOCK_PACKETSOCK_SEQPACKET等等(socket的类型有哪些?)。这个参数指定一个套接口的类型,套接口可能的类型有:SOCK_STREAM、SOCK_DGRAM、SOCK_SEQPACKET、SOCK_RAW等等,它们分别表明字节流、数据报、有序分组、原始套接口。这实际上是指定内核为我们提供的服务抽象,比如我们要一个字节流。需要注意的,并不是每一种协议簇都支持这里的所有的类型,所以类型与协议簇要匹配。
      SOCK_STREAM = 1,      /* Sequenced, reliable, connection-based  byte streams.  */ 提供双向连续且可信赖的数据流,即TCP
      SOCK_DGRAM = 2,       /* Connectionless, unreliable datagrams  of fixed maximum length.  */使用不连续不可信赖的数据包连接
      SOCK_RAW = 3,         /* Raw protocol interface.  */提供原始网络协议存取
      SOCK_RDM = 4,         /* Reliably-delivered messages.  */提供可信赖的数据包连接
      SOCK_SEQPACKET = 5,       /* Sequenced, reliable, connection-based,  datagrams of fixed maximum length.  */提供连续可信赖的数据包连接
      SOCK_PACKET = 10      /* Linux specific way of getting packets  at the dev level.  For writing rarp and  other similar things on the user level. */提供和网络驱动程序直接通信。

    • protocol:故名思意,就是指定协议。常用的协议有,IPPROTO_TCPIPPTOTO_UDPIPPROTO_SCTPIPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议(这个协议我将会单独开篇讨论!)。见usr/include/linux/in.h:
      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_EGP = 8,      /* Exterior Gateway Protocol        */
      IPPROTO_PUP = 12,     /* PUP protocol             */
      IPPROTO_UDP = 17,     /* User Datagram Protocol       */
      IPPROTO_IDP = 22,     /* XNS IDP protocol         */
      IPPROTO_RSVP = 46,        /* RSVP protocol            */
      IPPROTO_GRE = 47,     /* Cisco GRE tunnels (rfc 1701,1702)    */
      IPPROTO_IPV6   = 41,      /* IPv6-in-IPv4 tunnelling      */
      IPPROTO_PIM    = 103,     /* Protocol Independent Multicast   */
      IPPROTO_ESP = 50,            /* Encapsulation Security Payload protocol */
      IPPROTO_AH = 51,             /* Authentication Header protocol       */
      IPPROTO_COMP   = 108,                /* Compression Header protocol */
      IPPROTO_RAW    = 255,     /* Raw IP packets           */
      IPPROTO_MAX

    注意:并不是上面的type和protocol可以随意组合的,如SOCK_STREAM不可以跟IPPROTO_UDP组合。当protocol为0时,会自动选择type类型对应的默认协议。

    当我们调用socket创建一个socket时,返回的socket描述字它存在于协议族(address family,AF_XXX)空间中,但没有一个具体的地址。如果想要给它赋值一个地址,就必须调用bind()函数,否则就当调用connect()listen()时系统会自动随机分配一个端口。

    2、bind()函数

    int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen);

    参见/usr/include/bits/socket.h

    当一个套接字被创建后,存在一个名字空间(地址族),但它没有被命名。bind()将套接字地址(包括本地主机地址和本地端口地址)与所创建的套接字号联系起来,即将名字赋予套接字,以对socket定位。

    bind()用来设置给参数sockfd的socket一个名称。此名称由参数my_addr指向一sockaddr结构,对于不同的socket domain定义了一个通用的数据结构。
    struct sockaddr {
    unsigned short int sa_family;
    char sa_data[14];
    };
    sa_family 为调用socket()时的domain参数,即AF_xxxx值。
    sa_data 最多使用14个字符长度。
    此sockaddr结构会因使用不同的socket domain而有不同结构定义,例如使用AF_INET domain,其socketaddr结构定义便为
    struct socketaddr_in {
    unsigned short int sin_family;
    uint16_t sin_port;
    struct in_addr sin_addr;
    unsigned char sin_zero[8];
    };
    struct in_addr {
    uint32_t s_addr;
    };
    sin_family 即为sa_family
    sin_port 为使用的port编号
    sin_addr.s_addr 为IP 地址
    sin_zero 未使用。

    3、listen()函数

    int listen(int socket, int backlog);

    listen()用来等待参数socket的套接字连线。参数backlog指定同时能处理的最大连接要求,如果连接数目达此上限则client端将收到ECONNREFUSED的错误。Listen()并未开始接收连线,只是设置socket为listen模式,真正接收client端连线的是accept()。通常listen()会在socket(),bind()之后调用,接着才调用accept()。

    返回值
    成功则返回0,失败返回-1,错误原因存于errno
    附加说明
    listen()只适用SOCK_STREAM或SOCK_SEQPACKET的socket类型。如果socket为AF_INET则参数backlog 最大值可设至128。

    3、accept()函数

     int accept(int s, struct sockaddr *addr, socklen_t *addrlen);


    accept()用来接受参数s的socket连线。参数s的socket必需先经bind()、listen()函数处理过,当有连线进来时accept()会返回一个新的socket去处理代码,往后的数据传送与读取就是经由新的socket处理,而原来参数s的socket能继续使用accept()来接受新的连线要求。连线成功时,参数addr所指的结构会被系统填入远程主机的地址数据。参数addrlen为sockaddr的结构体长度。
    返回值:成功则返回新的socket,失败返回-1,错误原因存在于errno中。

    4、connect()函数

    int  connect(int  sockfd,  const struct sockaddr *serv_addr, socklen_t addrlen);
    connect()用来将参数sockfd 的socket 连至参数serv_addr 指定的网络地址。结构sockaddr请参考bind()。参数addrlen为sockaddr的结构长度。
    返回值
    成功则返回0,失败返回-1,错误原因存于errno中。

    5、send()函数 ,sendto()函数

           ssize_t send(int s, const void *buf, size_t len, int flags);
           ssize_t sendto(int s, const void *buf, size_t len, int flags, const struct sockaddr *to, socklen_t tolen);
           ssize_t sendmsg(int s, const struct msghdr *msg, int flags);
    send()用来将数据由指定的socket 传给对方主机。参数s为已建立好连接的socket。参数msg指向欲连线的数据内容,参数len则为数据长度。参数flags一般设0,其他数值定义如下:
    MSG_OOB传送的数据以out-of-band 送出。
    MSG_DONTROUTE取消路由表查询
    MSG_DONTWAIT设置为不可阻断运作
    MSG_NOSIGNAL此动作不愿被SIGPIPE 信号中断。
    返回值:成功则返回实际传送出去的字符数。
    sendto() 用来将数据由指定的socket传给对方主机。参数s为已建好连线的socket,如果利用UDP协议则不需经过连线操作。参数msg指向欲连线的数据内容,参数flags 一般设0,详细描述请参考send()。参数to用来指定欲传送的网络地址,结构sockaddr请参考bind()。参数tolen为sockaddr的结果长度。
    返回值:成功则返回实际传送出去的字符数,失败返回-1,错误原因存于errno 中。失败返回-1。错误原因存于errno。
    sendmsg()用来将数据由指定的socket传给对方主机。参数s为已建立好连线的socket,如果利用UDP协议则不需经过连线操作。参数msg 指向欲连线的数据结构内容,参数flags一般默认为0,详细描述请参考send()。
    结构msghdr定义如下
    struct msghdr {
    void *msg_name; /*Address to send to /receive from . */
    socklen_t msg_namelen; /* Length of addres data */
    strcut iovec * msg_iov; /* Vector of data to send/receive into */
    size_t msg_iovlen; /* Number of elements in the vector */
    void * msg_control; /* Ancillary dat */
    size_t msg_controllen; /* Ancillary data buffer length */
    int msg_flags; /* Flags on received message */
    };
    返回值:成功则返回实际传送出去的字符数,失败返回-1,错误原因存于errno

    6、recv()函数 ,recvfrom()函数

           ssize_t recv(int s, void *buf, size_t len, int flags);
           ssize_t recvfrom(int s, void *buf, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen);
           ssize_t recvmsg(int s, struct msghdr *msg, int flags);
    recv()用来接收远端主机经指定的socket传来的数据,并把数据存到由参数buf 指向的内存空间,参数len为可接收数据的最大长度。
    flags一般设0。其他数值定义如下:
    MSG_OOB接收以out-of-band 送出的数据。
    MSG_PEEK返回来的数据并不会在系统内删除,如果再调用recv()会返回相同的数据内容。
    MSG_WAITALL强迫接收到len大小的数据后才能返回,除非有错误或信号产生。
    MSG_NOSIGNAL此操作不愿被SIGPIPE信号中断返回值成功则返回接收到的字符数,失败返回-1,错误原因存于errno中。
    recv()用来接收远程主机经指定的socket 传来的数据,并把数据存到由参数buf 指向的内存空间,参数len 为可接收数据的最大长度。参数flags 一般设0,其他数值定义请参考recv()。参数from用来指定欲传送的网络地址,结构sockaddr 请参考bind()。参数fromlen为sockaddr的结构长度。
    返回值:成功则返回接收到的字符数,失败则返回-1,错误原因存于errno中。
    recvmsg()用来接收远程主机经指定的socket传来的数据。参数s为已建立好连线的socket,如果利用UDP协议则不需经过连线操作。参数msg指向欲连线的数据结构内容,参数flags一般设0,详细描述请参考send()。关于结构msghdr的定义请参考sendmsg()。
    返回值: 成功则返回接收到的字符数,失败则返回-1,错误原因存于errno中。

    ===========================================================================================
    endprotoent(结束网络协议数据的读取)
    相关函数 getprotoentgetprotobynamegetprotobynumbersetprotoent
    表头文件 #include<netdb.h>
    定义函数 void endprotoent(void);
    函数说明 endprotoent()用来关闭由getprotoent()打开的文件。
    返回值 无返回值
    范例 参考getprotoent()
    endservent(结束网络服务数据的读取)
    相关函数 getserventgetservbynamegetservbyportsetservent
    表头文件 #include<netdb.h>
    定义函数 void endservent(void);
    函数说明 endservent()用来关闭由getservent()所打开的文件。
    返回值 无返回值
    范例 参考getservent()
    getsockopt(取得socket状态)
    相关函数 setsockopt
    表头文件
    #include<sys/types.h>
    #include<sys/socket.h>
    定义函数 int getsockopt(int s,int level,int optname,void* optval,socklen_t* optlen);
    函数说明 getsockopt()会将参数s所指定的socket状态返回。参数optname代表欲取得何种选项状态,而参数optval则指向欲保存结果的内存地址,参数optlen则为该空间的大小。参数leveloptname请参考setsockopt()
    返回值 成功则返回0,若有错误则返回-1,错误原因存于errno
    错误代码
    EBADF参数并非合法的socket处理代码
    ENOTSOCK参数s为一文件描述词,非socket
    ENOPROTOOPT参数optname指定的选项不正确
    EFAULT参数optval指针指向无法存取的内存空间
    范例
    #include<sys/types.h>
    #include<sys/socket.h>
    main()
    {
    int s,optval,optlen = sizeof(int);
    if ((s = socket(AF_INET,SOCK_STREAM,0))<0)
    perror(“socket”);
    getsockopt(s,SOL_SOCKET,SO_TYPE,&optval,&optlen);
    printf(“optval = %d/n”,optval);
    close(s);
    }
    执行 optval = 1 /*SOCK_STREAM的定义正是此值*/
    htonl(将32位主机字符顺序转换成网络字符顺序)
    相关函数 htonsntohlntohs
    表头文件 #include<netinet/in.h>
    定义函数 unsigned long int htonl(unsigned long int hostlong);
    函数说明 Htonl()用来将参数指定的32hostlong 转换成网络字符顺序。
    返回值 返回对应的网络字符顺序。
    范例 参考getservbyport()connect()
    htons(将16位主机字符顺序转换成网络字符顺序)
    相关函数 htonlntohlntohs
    表头文件 #include<netinet/in.h>
    定义函数 unsigned short int htons(unsigned short int hostshort);
    函数说明 htons()用来将参数指定的16hostshort转换成网络字符顺序。
    返回值 返回对应的网络字符顺序。
    范例 参考connect()
    inet_addr(将网络地址转成二进制的数字)
    相关函数 inet_atoninet_ntoa
    表头文件
    #include<sys/socket.h>
    #include<netinet/in.h>
    #include<arpa/inet.h>
    定义函数 unsigned long int inet_addr(const char *cp);
    函数说明 inet_addr()用来将参数cp所指的网络地址字符串转换成网络所使用的二进制数字。网络地址字符串是以数字和点组成的字符串,例如:“163.13.132.68”
    返回值 成功则返回对应的网络二进制的数字,失败返回-1
    inet_aton(将网络地址转成网络二进制的数字)
    相关函数 inet_addrinet_ntoa
    表头文件
    #include<sys/scoket.h>
    #include<netinet/in.h>
    #include<arpa/inet.h>
    定义函数 int inet_aton(const char * cp,struct in_addr *inp);
    函数说明
    inet_aton()用来将参数cp所指的网络地址字符串转换成网络使用的二进制的数字,然后存于参数inp所指的in_addr结构中。
    结构in_addr定义如下
    struct in_addr {
    unsigned long int s_addr;
    };
    返回值 成功则返回非0值,失败则返回0
    inet_ntoa(将网络二进制的数字转换成网络地址)
    相关函数 inet_addrinet_aton
    表头文件
    #include<sys/socket.h>
    #include<netinet/in.h>
    #include<arpa/inet.h>
    定义函数 char * inet_ntoa(struct in_addr in);
    函数说明 inet_ntoa()用来将参数in所指的网络二进制的数字转换成网络地址,然后将指向此网络地址字符串的指针返回。
    返回值 成功则返回字符串指针,失败则返回NULL

    ntohs(将16位网络字符顺序转换成主机字符顺序)
    相关函数 htonlhtonsntohl
    表头文件 #include<netinet/in.h>
    定义函数 unsigned short int ntohs(unsigned short int netshort);
    函数说明 ntohs()用来将参数指定的16netshort转换成主机字符顺序。
    返回值 返回对应的主机顺序。
    范例 参考getservent()
    ntohl(将32位网络字符顺序转换成主机字符顺序)
    相关函数 htonl,htons,ntohs
    表头文件 #include<netinet/in.h>
    定义函数 unsigned long int ntohl(unsigned long int netlong);
    函数说明 ntohl()用来将参数指定的32位netlong转换成主机字符顺序。
    返回值 返回对应的主机字符顺序。
    范例 参考getservent()。


    setprotoent(打开网络协议的数据文件)
    相关函数 getprotobyname, getprotobynumber, endprotoent
    表头文件 #include <netdb.h>
    定义函数 void setprotoent (int stayopen);
    函数说明 setprotoent()用来打开/etc/protocols如果参数stayopen值为1,则接下来的getprotobyname()getprotobynumber()将不会自动关闭此文件。
    setservent(打开主机网络服务的数据文件)
    相关函数 getservent, getservbyname, getservbyport, endservent
    表头文件 #include < netdb.h >
    定义函数 void setservent (int stayopen);
    函数说明 setservent()用来打开/etc/services,如果参数stayopen值为1,则接下来的getservbyname()getservbyport()将补回自动关闭文件。
    setsockopt(设置socket状态)
    相关函数 getsockopt
    表头文件
    #include<sys/types.h>
    #include<sys/socket.h>
    定义函数 int setsockopt(int s,int level,int optname,const void * optval,,socklen_toptlen);
    函数说明
    setsockopt()用来设置参数s所指定的socket状态。参数level代表欲设置的网络层,一般设成SOL_SOCKET以存取socket层。参数optname代表欲设置的选项,有下列几种数值:
    SO_DEBUG打开或关闭排错模式
    SO_REUSEADDR允许在bind()过程中本地地址可重复使用
    SO_TYPE返回socket形态。
    SO_ERROR返回socket已发生的错误原因
    SO_DONTROUTE送出的数据包不要利用路由设备来传输。
    SO_BROADCAST使用广播方式传送
    SO_SNDBUF设置送出的暂存区大小
    SO_RCVBUF设置接收的暂存区大小
    SO_KEEPALIVE定期确定连线是否已终止。
    SO_OOBINLINE当接收到OOB 数据时会马上送至标准输入设备
    SO_LINGER确保数据安全且可靠的传送出去。
    参数 optval代表欲设置的值,参数optlen则为optval的长度。
    返回值 成功则返回0,若有错误则返回-1,错误原因存于errno
    附加说明
    EBADF参数s并非合法的socket处理代码。
    ENOTSOCK参数s为一文件描述词,非socket
    ENOPROTOOPT参数optname指定的选项不正确。
    EFAULT参数optval指针指向无法存取的内存空间。
    范例 参考getsockopt()
    shutdown(终止socket通信)
    相关函数 socketconnect
    表头文件 #include<sys/socket.h>
    定义函数 int shutdown(int s,int how);
    函数说明
    shutdown()用来终止参数s所指定的socket连线。参数s是连线中的socket处理代码,参数how有下列几种情况:
    how=0 终止读取操作。
    how=1 终止传送操作
    how=2 终止读取及传送操作
    返回值 成功则返回0,失败返回-1,错误原因存于errno
    错误代码
    EBADF参数s不是有效的socket处理代码。
    ENOTSOCK参数s为一文件描述词,非socket
    ENOTCONN参数s指定的socket并未连线。

    TCP协议:

    服务器端:tcp_server.c

    1. #include <stdio.h>  
    2. #include <sys/types.h>  
    3. #include <sys/socket.h>  
    4. #include <netinet/in.h>  
    5. #include <arpa/inet.h>  
    6.   
    7. int main(int argc, char *argv[])  
    8. {  
    9.     int server_sockfd;//服务器端套接字  
    10.     int client_sockfd;//客户端套接字  
    11.     int len;  
    12.     struct sockaddr_in my_addr;   //服务器网络地址结构体  
    13.     struct sockaddr_in remote_addr; //客户端网络地址结构体  
    14.     int sin_size;  
    15.     char buf[BUFSIZ];  //数据传送的缓冲区  
    16.     memset(&my_addr,0,sizeof(my_addr)); //数据初始化--清零  
    17.     my_addr.sin_family=AF_INET; //设置为IP通信  
    18.     my_addr.sin_addr.s_addr=INADDR_ANY;//服务器IP地址--允许连接到所有本地地址上  
    19.     my_addr.sin_port=htons(8000); //服务器端口号  
    20.       
    21.     /*创建服务器端套接字--IPv4协议,面向连接通信,TCP协议*/  
    22.     if((server_sockfd=socket(PF_INET,SOCK_STREAM,0))<0)  
    23.     {    
    24.         perror("socket");  
    25.         return 1;  
    26.     }  
    27.    
    28.         /*将套接字绑定到服务器的网络地址上*/  
    29.     if (bind(server_sockfd,(struct sockaddr *)&my_addr,sizeof(struct sockaddr))<0)  
    30.     {  
    31.         perror("bind");  
    32.         return 1;  
    33.     }  
    34.       
    35.     /*监听连接请求--监听队列长度为5*/  
    36.     listen(server_sockfd,5);  
    37.       
    38.     sin_size=sizeof(struct sockaddr_in);  
    39.       
    40.     /*等待客户端连接请求到达*/  
    41.     if((client_sockfd=accept(server_sockfd,(struct sockaddr *)&remote_addr,&sin_size))<0)  
    42.     {  
    43.         perror("accept");  
    44.         return 1;  
    45.     }  
    46.     printf("accept client %s/n",inet_ntoa(remote_addr.sin_addr));  
    47.     len=send(client_sockfd,"Welcome to my server/n",21,0);//发送欢迎信息  
    48.       
    49.     /*接收客户端的数据并将其发送给客户端--recv返回接收到的字节数,send返回发送的字节数*/  
    50.     while((len=recv(client_sockfd,buf,BUFSIZ,0))>0))  
    51.     {  
    52.         buf[len]='/0';  
    53.         printf("%s/n",buf);  
    54.         if(send(client_sockfd,buf,len,0)<0)  
    55.         {  
    56.             perror("write");  
    57.             return 1;  
    58.         }  
    59.     }  
    60.     close(client_sockfd);  
    61.     close(server_sockfd);  
    62.         return 0;  
    63. }  

    TCP协议:

    客户端:tcp_client.c

    [c-sharp] view plaincopy
    1. #include <stdio.h>  
    2. #include <sys/types.h>  
    3. #include <sys/socket.h>  
    4. #include <netinet/in.h>  
    5. #include <arpa/inet.h>  
    6.   
    7. int main(int argc, char *argv[])  
    8. {  
    9.     int client_sockfd;  
    10.     int len;  
    11.     struct sockaddr_in remote_addr; //服务器端网络地址结构体  
    12.     char buf[BUFSIZ];  //数据传送的缓冲区  
    13.     memset(&remote_addr,0,sizeof(remote_addr)); //数据初始化--清零  
    14.     remote_addr.sin_family=AF_INET; //设置为IP通信  
    15.     remote_addr.sin_addr.s_addr=inet_addr("127.0.0.1");//服务器IP地址  
    16.     remote_addr.sin_port=htons(8000); //服务器端口号  
    17.       
    18.     /*创建客户端套接字--IPv4协议,面向连接通信,TCP协议*/  
    19.     if((client_sockfd=socket(PF_INET,SOCK_STREAM,0))<0)  
    20.     {  
    21.         perror("socket");  
    22.         return 1;  
    23.     }  
    24.       
    25.     /*将套接字绑定到服务器的网络地址上*/  
    26.     if(connect(client_sockfd,(struct sockaddr *)&remote_addr,sizeof(struct sockaddr))<0)  
    27.     {  
    28.         perror("connect");  
    29.         return 1;  
    30.     }  
    31.     printf("connected to server/n");  
    32.     len=recv(client_sockfd,buf,BUFSIZ,0);//接收服务器端信息  
    33.          buf[len]='/0';  
    34.     printf("%s",buf); //打印服务器端信息  
    35.       
    36.     /*循环的发送接收信息并打印接收信息--recv返回接收到的字节数,send返回发送的字节数*/  
    37.     while(1)  
    38.     {  
    39.         printf("Enter string to send:");  
    40.         scanf("%s",buf);  
    41.         if(!strcmp(buf,"quit")  
    42.             break;  
    43.         len=send(client_sockfd,buf,strlen(buf),0);  
    44.         len=recv(client_sockfd,buf,BUFSIZ,0);  
    45.         buf[len]='/0';  
    46.         printf("received:%s/n",buf);  
    47.     }  
    48.     close(client_sockfd);//关闭套接字  
    49.          return 0;  
    50. }  

    UDP协议:

    服务器端:udp_server.c

     

    1. #include <stdio.h>  
    2. #include <sys/types.h>  
    3. #include <sys/socket.h>  
    4. #include <netinet/in.h>  
    5. #include <arpa/inet.h>  
    6.   
    7. int main(int argc, char *argv[])  
    8. {  
    9.     int server_sockfd;  
    10.     int len;  
    11.     struct sockaddr_in my_addr;   //服务器网络地址结构体  
    12.          struct sockaddr_in remote_addr; //客户端网络地址结构体  
    13.     int sin_size;  
    14.     char buf[BUFSIZ];  //数据传送的缓冲区  
    15.     memset(&my_addr,0,sizeof(my_addr)); //数据初始化--清零  
    16.     my_addr.sin_family=AF_INET; //设置为IP通信  
    17.     my_addr.sin_addr.s_addr=INADDR_ANY;//服务器IP地址--允许连接到所有本地地址上  
    18.     my_addr.sin_port=htons(8000); //服务器端口号  
    19.       
    20.     /*创建服务器端套接字--IPv4协议,面向无连接通信,UDP协议*/  
    21.     if((server_sockfd=socket(PF_INET,SOCK_DGRAM,0))<0)  
    22.     {    
    23.         perror("socket");  
    24.         return 1;  
    25.     }  
    26.    
    27.         /*将套接字绑定到服务器的网络地址上*/  
    28.     if (bind(server_sockfd,(struct sockaddr *)&my_addr,sizeof(struct sockaddr))<0)  
    29.     {  
    30.         perror("bind");  
    31.         return 1;  
    32.     }  
    33.     sin_size=sizeof(struct sockaddr_in);  
    34.     printf("waiting for a packet.../n");  
    35.       
    36.     /*接收客户端的数据并将其发送给客户端--recvfrom是无连接的*/  
    37.     if((len=recvfrom(server_sockfd,buf,BUFSIZ,0,(struct sockaddr *)&remote_addr,&sin_size))<0)  
    38.     {  
    39.         perror("recvfrom");   
    40.         return 1;  
    41.     }  
    42.     printf("received packet from %s:/n",inet_ntoa(remote_addr.sin_addr));  
    43.     buf[len]='/0';  
    44.     printf("contents: %s/n",buf);  
    45.     close(server_sockfd);  
    46.         return 0;  
    47. }  

     

    客户端:udp_client.c

    1. #include <stdio.h>  
    2. #include <sys/types.h>  
    3. #include <sys/socket.h>  
    4. #include <netinet/in.h>  
    5. #include <arpa/inet.h>  
    6.   
    7. int main(int argc, char *argv[])  
    8. {  
    9.     int client_sockfd;  
    10.     int len;  
    11.         struct sockaddr_in remote_addr; //服务器端网络地址结构体  
    12.     int sin_size;  
    13.     char buf[BUFSIZ];  //数据传送的缓冲区  
    14.     memset(&remote_addr,0,sizeof(remote_addr)); //数据初始化--清零  
    15.     remote_addr.sin_family=AF_INET; //设置为IP通信  
    16.     remote_addr.sin_addr.s_addr=inet_addr("127.0.0.1");//服务器IP地址  
    17.     remote_addr.sin_port=htons(8000); //服务器端口号  
    18.   
    19.          /*创建客户端套接字--IPv4协议,面向无连接通信,UDP协议*/  
    20.     if((client_sockfd=socket(PF_INET,SOCK_DGRAM,0))<0)  
    21.     {    
    22.         perror("socket");  
    23.         return 1;  
    24.     }  
    25.     strcpy(buf,"This is a test message");  
    26.     printf("sending: '%s'/n",buf);  
    27.     sin_size=sizeof(struct sockaddr_in);  
    28.       
    29.     /*向服务器发送数据包*/  
    30.     if((len=sendto(client_sockfd,buf,strlen(buf),0,(struct sockaddr *)&remote_addr,sizeof(struct sockaddr)))<0)  
    31.     {  
    32.         perror("recvfrom");   
    33.         return 1;  
    34.     }  
    35.     close(client_sockfd);  
    36.     return 0;  
    37. }  
      




    展开全文
  • Linux socket编程

    2018-09-18 16:29:01
    1、Linux/Unix Socket 函数库  Linux Socket 函数库是从Berkeley大学开发的BSD Linux系统中移植过来的。BSD Socket 接口在众多Unix系统中 被广泛支持的TCP/IP通信接口,Linux下的Socket程序设计也适用于大多数其他...

    1、Linux/Unix Socket 函数库
        Linux Socket 函数库是从Berkeley大学开发的BSD Linux系统中移植过来的。BSD Socket 接口在众多Unix系统中
    被广泛支持的TCP/IP通信接口,Linux下的Socket程序设计也适用于大多数其他的Unix系统。

     

    2、Socket编程原理
         套接口网络编程原理
         套接口有三种类型:流式套接口、数据报套接口以及原始套接口。
         流式套接口定义了一种可靠的面向连接的服务,实现了无差错无重复的顺序数据传输。
    数据报套接口定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且
    不保证可靠,无差错。原始接口允许对底层协议如IP或者ICMP直接访问,主要用于新的网络协
    议实现测试等。
          无连接服务器一般都是面向事物处理,一个请求与一个应答就完成了客户程序与服务程
    序之间的相互作用。使用无连接的套接口编程,程序的流程可以用如下表示。

    面向连接的服务器处理请求比较复杂,使用面向连接的套接口编程,流程如下表示

              

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

     

    3、客户端例程

    #include <stdio.h>
    #include <string.h>
    #include <errno.h>
    #include <netinet/in.h>

    #define PORT 12345
    #define HOST_ADDR "192.168.47.187"

    void main()
    {
        struct sockaddr_in server;
        int s, ns;
        int pktlen, buflen;

        char buf1[256], buf2[256];
        
        s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
        server.sin_family = AF_INET;
        server.sin_port = htons(PORT);
        server.sin_addr.s_addr = inet_addr(HOST_ADDR);

        if(connect(s, (struct sockaddr *)&server, sizeof(server)) < 0)
        {
            perror("connect()");
            return;
        }

        while(1)
        {
            printf("Input send data:");
            gets(buf1);
            buflen = strlen(buf1);
            if(buflen == 0)
            {
                break;
            }
            send(s, buf1, buflen+1, 0);
        }

        close(s);
    }

    4、服务端例程

    #include <stdio.h>
    #include <string.h>
    #include <netinet/in.h>

    #define PORT     12345

    void main()
    {
        struct sockaddr_in client, server;
        int s, ns, namelen, pktlen;
        char buf[256];

        s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
        memset(&server, 0, sizeof(server));
        server.sin_family = AF_INET;
        server.sin_port = htons(PORT);
        server.sin_addr.s_addr = INADDR_ANY;
        bind(s, (struct sockaddr *)&server, sizeof(server));
        listen(s, 1);
        namelen = sizeof(client);
        ns = accept(s, (struct sockaddr *)&client, &namelen);

        while(1)
        {
            pktlen = read(ns, buf, sizeof(buf), 0);
            if(pktlen == 0)
                break;
            printf("Receive tcp data: %s \n", buf);
            write(ns, buf, pktlen, 0);
            memset(buf, 0, sizeof(buf));
        }
        printf("close tcp link %d\n", ns);
        close(ns);
        close(s);
    }

     

     

     

     

     

    展开全文
  • Linux Socket编程

    千次阅读 2018-01-02 19:51:20
    Linux Socket编程 “一切皆Socket!” 话虽些许夸张,但是事实也是,现在的网络编程几乎都是用的socket。 ——有感于实际编程和开源项目研究。 我们深谙信息交流的价值,那网络中进程之间如何通信,如我们每天...

    Linux Socket编程


    “一切皆Socket!”

    话虽些许夸张,但是事实也是,现在的网络编程几乎都是用的socket。

    ——有感于实际编程和开源项目研究。

    我们深谙信息交流的价值,那网络中进程之间如何通信,如我们每天打开浏览器浏览网页时,浏览器的进程怎么与web服务器通信的?当你用QQ聊天时,QQ进程怎么与服务器或你好友所在的QQ进程通信?这些都得靠socket?那什么是socket?socket的类型有哪些?还有socket的基本函数,这些都是本文想介绍的。本文的主要内容如下:

    • 1、网络中进程之间如何通信?
    • 2、Socket是什么?
    • 3、socket的基本操作
      • 3.1、socket()函数
      • 3.2、bind()函数
      • 3.3、listen()、connect()函数
      • 3.4、accept()函数
      • 3.5、read()、write()函数等
      • 3.6、close()函数
    • 4、socket中TCP的三次握手建立连接详解
    • 5、socket中TCP的四次握手释放连接详解
    • 6、一个例子(实践一下)
    • 7、留下一个问题,欢迎大家回帖回答!!!

    1、网络中进程之间如何通信?

    本地的进程间通信(IPC)有很多种方式,但可以总结为下面4类:

    • 消息传递(管道、FIFO、消息队列)
    • 同步(互斥量、条件变量、读写锁、文件和写记录锁、信号量)
    • 共享内存(匿名的和具名的)
    • 远程过程调用(Solaris门和Sun RPC)

    但这些都不是本文的主题!我们要讨论的是网络中进程之间如何通信?首要解决的问题是如何唯一标识一个进程,否则通信无从谈起!在本地可以通过进程PID来唯一标识一个进程,但是在网络中这是行不通的。其实TCP/IP协议族已经帮我们解决了这个问题,网络层的“ip地址可以唯一标识网络中的主机,而传输层的“协议+端口可以唯一标识主机中的应用程序(进程)。这样利用三元组(ip地址,协议,端口)就可以标识网络的进程了,网络中的进程通信就可以利用这个标志与其它进程进行交互。

    使用TCP/IP协议的应用程序通常采用应用编程接口:UNIX  BSD的套接字(socket)和UNIX System V的TLI(已经被淘汰),来实现网络进程之间的通信。就目前而言,几乎所有的应用程序都是采用socket,而现在又是网络时代,网络中进程通信是无处不在,这就是我为什么说“一切皆socket”。

    2、什么是Socket?

    上面我们已经知道网络中的进程是通过socket来通信的,那什么是socket呢?socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。我的理解就是Socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭),这些函数我们在后面进行介绍。

    socket一词的起源

    在组网领域的首次使用是在1970年2月12日发布的文献IETF RFC33中发现的,撰写者为Stephen Carr、Steve Crocker和Vint Cerf。根据美国计算机历史博物馆的记载,Croker写道:“命名空间的元素都可称为套接字接口。一个套接字接口构成一个连接的一端,而一个连接可完全由一对套接字接口规定。”计算机历史博物馆补充道:“这比BSD的套接字接口定义早了大约12年。”

    3、socket的基本操作

    既然socket是“open—write/read—close”模式的一种实现,那么socket就提供了这些操作对应的函数接口。下面以TCP为例,介绍几个基本的socket接口函数。

    3.1、socket()函数

    int socket(int domain, int type, int protocol);

    socket函数对应于普通文件的打开操作。普通文件的打开操作返回一个文件描述字,而socket()用于创建一个socket描述符(socket descriptor),它唯一标识一个socket。这个socket描述字跟文件描述字一样,后续的操作都有用到它,把它作为参数,通过它来进行一些读写操作。

    正如可以给fopen的传入不同参数值,以打开不同的文件。创建socket的时候,也可以指定不同的参数创建不同的socket描述符,socket函数的三个参数分别为:

    • domain:即协议域,又称为协议族(family)。常用的协议族有,AF_INETAF_INET6AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。
    • type:指定socket类型。常用的socket类型有,SOCK_STREAMSOCK_DGRAMSOCK_RAWSOCK_PACKETSOCK_SEQPACKET等等(socket的类型有哪些?)。
    • protocol:故名思意,就是指定协议。常用的协议有,IPPROTO_TCPIPPTOTO_UDPIPPROTO_SCTPIPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议(这个协议我将会单独开篇讨论!)。

    注意:并不是上面的type和protocol可以随意组合的,如SOCK_STREAM不可以跟IPPROTO_UDP组合。当protocol为0时,会自动选择type类型对应的默认协议。

    当我们调用socket创建一个socket时,返回的socket描述字它存在于协议族(address family,AF_XXX)空间中,但没有一个具体的地址。如果想要给它赋值一个地址,就必须调用bind()函数,否则就当调用connect()listen()时系统会自动随机分配一个端口。

    3.2、bind()函数

    正如上面所说bind()函数把一个地址族中的特定地址赋给socket。例如对应AF_INETAF_INET6就是把一个ipv4或ipv6地址和端口号组合赋给socket。

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

    函数的三个参数分别为:

    • sockfd:即socket描述字,它是通过socket()函数创建了,唯一标识一个socket。bind()函数就是将给这个描述字绑定一个名字。
    • addr:一个const struct sockaddr *指针,指向要绑定给sockfd的协议地址。这个地址结构根据地址创建socket时的地址协议族的不同而不同,如ipv4对应的是: 
      struct sockaddr_in {
          sa_family_t    sin_family; /* address family: AF_INET */
          in_port_t      sin_port;   /* port in network byte order */
          struct in_addr sin_addr;   /* internet address */
      };
      
      /* Internet address. */
      struct in_addr {
          uint32_t       s_addr;     /* address in network byte order */
      };
      ipv6对应的是: 
      struct sockaddr_in6 { 
          sa_family_t     sin6_family;   /* AF_INET6 */ 
          in_port_t       sin6_port;     /* port number */ 
          uint32_t        sin6_flowinfo; /* IPv6 flow information */ 
          struct in6_addr sin6_addr;     /* IPv6 address */ 
          uint32_t        sin6_scope_id; /* Scope ID (new in 2.4) */ 
      };
      
      struct in6_addr { 
          unsigned char   s6_addr[16];   /* IPv6 address */ 
      };
      Unix域对应的是: 
      #define UNIX_PATH_MAX    108
      
      struct sockaddr_un { 
          sa_family_t sun_family;               /* AF_UNIX */ 
          char        sun_path[UNIX_PATH_MAX];  /* pathname */ 
      };
    • addrlen:对应的是地址的长度。

    通常服务器在启动的时候都会绑定一个众所周知的地址(如ip地址+端口号),用于提供服务,客户就可以通过它来接连服务器;而客户端就不用指定,有系统自动分配一个端口号和自身的ip地址组合。这就是为什么通常服务器端在listen之前会调用bind(),而客户端就不会调用,而是在connect()时由系统随机生成一个。

    网络字节序与主机字节序

    主机字节序就是我们平常说的大端和小端模式:不同的CPU有不同的字节序类型,这些字节序是指整数在内存中保存的顺序,这个叫做主机序。引用标准的Big-Endian和Little-Endian的定义如下:

      a) Little-Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。

      b) Big-Endian就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。

    网络字节序:4个字节的32 bit值以下面的次序传输:首先是0~7bit,其次8~15bit,然后16~23bit,最后是24~31bit。这种传输次序称作大端字节序。由于TCP/IP首部中所有的二进制整数在网络中传输时都要求以这种次序,因此它又称作网络字节序。字节序,顾名思义字节的顺序,就是大于一个字节类型的数据在内存中的存放顺序,一个字节的数据没有顺序的问题了。

    所以:在将一个地址绑定到socket的时候,请先将主机字节序转换成为网络字节序,而不要假定主机字节序跟网络字节序一样使用的是Big-Endian。由于这个问题曾引发过血案!公司项目代码中由于存在这个问题,导致了很多莫名其妙的问题,所以请谨记对主机字节序不要做任何假定,务必将其转化为网络字节序再赋给socket。

    3.3、listen()、connect()函数

    如果作为一个服务器,在调用socket()bind()之后就会调用listen()来监听这个socket,如果客户端这时调用connect()发出连接请求,服务器端就会接收到这个请求。

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

    listen函数的第一个参数即为要监听的socket描述字,第二个参数为相应socket可以排队的最大连接个数。socket()函数创建的socket默认是一个主动类型的,listen函数将socket变为被动类型的,等待客户的连接请求。

    connect函数的第一个参数即为客户端的socket描述字,第二参数为服务器的socket地址,第三个参数为socket地址的长度。客户端通过调用connect函数来建立与TCP服务器的连接。

    3.4、accept()函数

    TCP服务器端依次调用socket()bind()listen()之后,就会监听指定的socket地址了。TCP客户端依次调用socket()connect()之后就想TCP服务器发送了一个连接请求。TCP服务器监听到这个请求之后,就会调用accept()函数取接收请求,这样连接就建立好了。之后就可以开始网络I/O操作了,即类同于普通文件的读写I/O操作。

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

    accept函数的第一个参数为服务器的socket描述字,第二个参数为指向struct sockaddr *的指针,用于返回客户端的协议地址,第三个参数为协议地址的长度。如果accpet成功,那么其返回值是由内核自动生成的一个全新的描述字,代表与返回客户的TCP连接。

    注意:accept的第一个参数为服务器的socket描述字,是服务器开始调用socket()函数生成的,称为监听socket描述字;而accept函数返回的是已连接的socket描述字。一个服务器通常通常仅仅只创建一个监听socket描述字,它在该服务器的生命周期内一直存在。内核为每个由服务器进程接受的客户连接创建了一个已连接socket描述字,当服务器完成了对某个客户的服务,相应的已连接socket描述字就被关闭。

    3.5、read()、write()等函数

    万事具备只欠东风,至此服务器与客户已经建立好连接了。可以调用网络I/O进行读写操作了,即实现了网咯中不同进程之间的通信!网络I/O操作有下面几组:

    • read()/write()
    • recv()/send()
    • readv()/writev()
    • recvmsg()/sendmsg()
    • recvfrom()/sendto()

    我推荐使用recvmsg()/sendmsg()函数,这两个函数是最通用的I/O函数,实际上可以把上面的其它函数都替换成这两个函数。它们的声明如下:

           #include <unistd.h>
    
           ssize_t read(int fd, void *buf, size_t count);
           ssize_t write(int fd, const void *buf, size_t count);
    
           #include <sys/types.h>
           #include <sys/socket.h>
    
           ssize_t send(int sockfd, const void *buf, size_t len, int flags);
           ssize_t recv(int sockfd, void *buf, size_t len, int flags);
    
           ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                          const struct sockaddr *dest_addr, socklen_t addrlen);
           ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                            struct sockaddr *src_addr, socklen_t *addrlen);
    
           ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
           ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
    

    read函数是负责从fd中读取内容.当读成功时,read返回实际所读的字节数,如果返回的值是0表示已经读到文件的结束了,小于0表示出现了错误。如果错误为EINTR说明读是由中断引起的,如果是ECONNREST表示网络连接出了问题。

    write函数将buf中的nbytes字节内容写入文件描述符fd.成功时返回写的字节数。失败时返回-1,并设置errno变量。 在网络程序中,当我们向套接字文件描述符写时有俩种可能。1)write的返回值大于0,表示写了部分或者是全部的数据。2)返回的值小于0,此时出现了错误。我们要根据错误类型来处理。如果错误为EINTR表示在写的时候出现了中断错误。如果为EPIPE表示网络连接出现了问题(对方已经关闭了连接)。

    其它的我就不一一介绍这几对I/O函数了,具体参见man文档或者baidu、Google,下面的例子中将使用到send/recv。

    3.6、close()函数

    在服务器与客户端建立连接之后,会进行一些读写操作,完成了读写操作就要关闭相应的socket描述字,好比操作完打开的文件要调用fclose关闭打开的文件。

    #include <unistd.h>
    int close(int fd);

    close一个TCP socket的缺省行为时把该socket标记为以关闭,然后立即返回到调用进程。该描述字不能再由调用进程使用,也就是说不能再作为read或write的第一个参数。

    注意:close操作只是使相应socket描述字的引用计数-1,只有当引用计数为0的时候,才会触发TCP客户端向服务器发送终止连接请求。

    4、socket中TCP的三次握手建立连接详解

    我们知道tcp建立连接要进行“三次握手”,即交换三个分组。大致流程如下:

    • 客户端向服务器发送一个SYN J
    • 服务器向客户端响应一个SYN K,并对SYN J进行确认ACK J+1
    • 客户端再想服务器发一个确认ACK K+1

    只有就完了三次握手,但是这个三次握手发生在socket的那几个函数中呢?请看下图:

    image

    图1、socket中发送的TCP三次握手

    从图中可以看出,当客户端调用connect时,触发了连接请求,向服务器发送了SYN J包,这时connect进入阻塞状态;服务器监听到连接请求,即收到SYN J包,调用accept函数接收请求向客户端发送SYN K ,ACK J+1,这时accept进入阻塞状态;客户端收到服务器的SYN K ,ACK J+1之后,这时connect返回,并对SYN K进行确认;服务器收到ACK K+1时,accept返回,至此三次握手完毕,连接建立。

    总结:客户端的connect在三次握手的第二个次返回,而服务器端的accept在三次握手的第三次返回。

    5、socket中TCP的四次握手释放连接详解

    上面介绍了socket中TCP的三次握手建立过程,及其涉及的socket函数。现在我们介绍socket中的四次握手释放连接的过程,请看下图:

    image

    图2、socket中发送的TCP四次握手

    图示过程如下:

    • 某个应用进程首先调用close主动关闭连接,这时TCP发送一个FIN M;
    • 另一端接收到FIN M之后,执行被动关闭,对这个FIN进行确认。它的接收也作为文件结束符传递给应用进程,因为FIN的接收意味着应用进程在相应的连接上再也接收不到额外数据;
    • 一段时间之后,接收到文件结束符的应用进程调用close关闭它的socket。这导致它的TCP也发送一个FIN N;
    • 接收到这个FIN的源发送端TCP对它进行确认。

    这样每个方向上都有一个FIN和ACK。

    6、一个例子(实践一下)

    说了这么多了,动手实践一下。下面编写一个简单的服务器、客户端(使用TCP)——服务器端一直监听本机的6666号端口,如果收到连接请求,将接收请求并接收客户端发来的消息;客户端与服务器端建立连接并发送一条消息。

    服务器端代码:

    //服务器端
    
    #include<stdio.h>
    #include<stdlib.h>
    #include<string.h>
    #include<errno.h>
    #include<sys/types.h>
    #include<sys/socket.h>
    #include<netinet/in.h>
    
    #define MAXLINE 4096
    
    int main(int argc, char** argv)
    {
        int    listenfd, connfd;
        struct sockaddr_in     servaddr;
        char    buff[4096];
        int     n;
    
        if( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1 ){
        printf("create socket error: %s(errno: %d)\n",strerror(errno),errno);
        exit(0);
        }
    
        memset(&servaddr, 0, sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
        servaddr.sin_port = htons(6666);
    
        if( bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1){
        printf("bind socket error: %s(errno: %d)\n",strerror(errno),errno);
        exit(0);
        }
    
        if( listen(listenfd, 10) == -1){
        printf("listen socket error: %s(errno: %d)\n",strerror(errno),errno);
        exit(0);
        }
    
        printf("======waiting for client's request======\n");
        while(1){
        if( (connfd = accept(listenfd, (struct sockaddr*)NULL, NULL)) == -1){
            printf("accept socket error: %s(errno: %d)",strerror(errno),errno);
            continue;
        }
        n = recv(connfd, buff, MAXLINE, 0);
        buff[n] = '\0';
        printf("recv msg from client: %s\n", buff);
        close(connfd);
        }
    
        close(listenfd);
    }


    客户端代码:

    //客户端
    
    #include<stdio.h>
    #include<stdlib.h>
    #include<string.h>
    #include<errno.h>
    #include<sys/types.h>
    #include<sys/socket.h>
    #include<netinet/in.h>
    
    #define MAXLINE 4096
    
    int main(int argc, char** argv)
    {
        int    sockfd, n;
        char    recvline[4096], sendline[4096];
        struct sockaddr_in    servaddr;
    
        if( argc != 2){
        printf("usage: ./client <ipaddress>\n");
        exit(0);
        }
    
        if( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
        printf("create socket error: %s(errno: %d)\n", strerror(errno),errno);
        exit(0);
        }
    
        memset(&servaddr, 0, sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        servaddr.sin_port = htons(6666);
        if( inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0){
        printf("inet_pton error for %s\n",argv[1]);
        exit(0);
        }
    
        if( connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0){
        printf("connect error: %s(errno: %d)\n",strerror(errno),errno);
        exit(0);
        }
    
        printf("send msg to server: \n");
        fgets(sendline, 4096, stdin);
        if( send(sockfd, sendline, strlen(sendline), 0) < 0)
        {
        printf("send msg error: %s(errno: %d)\n", strerror(errno), errno);
        exit(0);
        }
    
        close(sockfd);
        exit(0);
    }


    当然上面的代码很简单,也有很多缺点,这就只是简单的演示socket的基本函数使用。其实不管有多复杂的网络程序,都使用的这些基本函数。上面的服务器使用的是迭代模式的,即只有处理完一个客户端请求才会去处理下一个客户端的请求,这样的服务器处理能力是很弱的,现实中的服务器都需要有并发处理能力!为了需要并发处理,服务器需要fork()一个新的进程或者线程去处理请求等。

    7、动动手

    留下一个问题,欢迎大家回帖回答!!!是否熟悉Linux下网络编程?如熟悉,编写如下程序完成如下功能:

    服务器端:

    接收地址192.168.100.2的客户端信息,如信息为“Client Query”,则打印“Receive Query”

    客户端:

    向地址192.168.100.168的服务器端顺序发送信息“Client Query test”,“Cleint Query”,“Client Query Quit”,然后退出。

    题目中出现的ip地址可以根据实际情况定。

    ——本文只是介绍了简单的socket编程。

    更为复杂的需要自己继续深入。

    (unix domain socket)使用udp发送>=128K的消息会报ENOBUFS的错误(一个实际socket编程中遇到的问题,希望对你有帮助)


    作者:吴秦
    出处:http://www.cnblogs.com/skynet/
    本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名吴秦(包含链接).

    展开全文
  • Linux Socket编程实战第1季第1部分

    千人学习 2018-01-22 21:49:43
    3、网络技术方面初级的一步步进入linux socket编程的世界; 本课程是linux socket编程的一小部分,从无名套接口开始, 然后逐步深入,这应该是很多课程所没有的。 以通俗的比照讲清楚一些概念,更多的是如何一步步...
  • Linux Socket基础介绍

    千次阅读 2015-05-09 16:35:52
    Linux Socket基础介绍!
  • 实战Linux Socket编程

    2008-09-23 18:18:37
    实战Linux Socket编程,主要讲述Linux系统下socket通信编程过程以及相关实践
  • Windows SocketLinux Socket编程的区别

    千次阅读 2017-08-14 16:03:27
    来源:Windows SocketLinux Socket编程的区别 SOCKET在原理上应该是一样的,只是不同系统的运行机置有些不同。 Socket 编程 windows到Linux代码移植遇到的问题 1、一些常用函数的移植  ...
  • linux socket can程序cantool

    千次阅读 热门讨论 2015-11-20 17:29:23
    最近写了个自认为不错的基于linux socket can程序,主要功能: 1. 程序具备全部CAN功能,包括CAN标准帧/扩展帧接收与发送、CAN总线错误判断、环回等功能 2. 适用基于LINUX SOCKET机制实现的CAN接口,可用于嵌入式...
  • 实战linux socket 编程 pdf版本

    热门讨论 2008-11-15 19:26:24
    实战 linux socket 编程 pdf格式
  • Linux Socket CAN——驱动开发

    千次阅读 2019-02-11 14:12:41
    Linux Socket CAN驱动开发 一 CAN总线协议 CAN是Controller Area Network(控制器局域网)的缩写。CAN通信协议在1986年由德国电气商博世公司所开发,主要面向汽车的通信系统。 现已是ISO国际标准化的串行通信协议。...
  • linux socket 编程顶尖教程

    千次阅读 2015-12-10 22:01:40
    原文地址:linux socket 编程全球顶尖教程!!! http://bbs.chinaunix.net/thread-1729902-1-1.html
  • linux socket 函数封装

    千次阅读 2013-01-09 14:51:30
    1、linux socket函数介绍 最近一直在看《unix网络编程》,有感于书中例子给出的创建tcp连接时对socket函数的封装。对于在服务器端编写网络程序的人来说,将这些函数进行封装能大大提高编程效率,而且还能简化编程...
  • LinuxSocket编程 IBM文档库: Linux socket 编程,第一部分 http://www.ibm.com/developerworks/cn/education/linux/l-sock/index.html Linux socket 编程,第二部分 http://www.ibm.com/develope
  • windows socket编程和linux socket编程的异同 待续
  • Linux Socket CAN——canutils嵌入式移植

    千次阅读 2019-01-27 16:23:37
    Linux Socket CAN——canutils嵌入式移植 Canutils是基于GNU GPLv2许可的开源代码,包括canconfig、canecho、cansend、candump、cansequence五个工具,用于检测和监控Socket CAN接口。本平台采用arm64处理器,故...
  • linux socket中关闭连接

    千次阅读 2014-01-09 10:23:06
    linux socket中关闭连接 分类: C/C++  关闭socket连接,实际上并不是很见到的事情。这涉及到如下的问题,多个进程共享socket时如何关闭socket;关闭通信链路与socket描述符的回收。  实际上,关闭socket...
  • “一切皆Socket!” 话虽些许夸张,但是事实也是,现在的网络编程几乎都是用的socket。 ——有感于实际编程和开源项目研究。 我们深谙信息交流的价值,那网络中进程之间如何通信,如我们每天打开浏览器浏览网页时...
  • Linux socket socket(2) (翻译 man 2)

    千次阅读 2010-12-03 14:38:00
    Linux socket socket(2) (翻译 man 2)
  • linux socket编程之listen函数

    千次阅读 2016-09-21 21:09:35
    linux socket:相关的一些函数的介绍,是从linux的man帮助文档中翻译来的,如果有不正确的地方,欢迎指正。 listen  #include int listen(int sockfd, int backlog); 返回值 :  成功返回0,失败返回-1,并将errno...
  • About AF_NETLINK in Linux Socket

    千次阅读 2017-08-13 22:14:16
    About AF_NETLINK in Linux Socket 由于开发和维护内核的复杂性,只把最为关键同时对性能要求最高的代码放进内核中。其他的诸如GUI,管理和控制代码,通常放在用户空间运行。这种将实现分离在内核和用户空间的思想在...
  • Linux socket API 使用实例

    千次阅读 2010-06-01 09:50:00
    Linux socket API 使用实例
  • linux socket 缓冲区默认大小

    千次阅读 2015-11-23 18:17:08
    linux socket 缓冲区默认大小 2012-11-04 16:07 6554人阅读 评论(0) 收藏 举报 分类: Linux(39) 版权声明:本文为博主原创文章,未经博主允许不得转载。 1. tcp 收发缓冲区...
  • linux Socket 中 setsockopt的SO_KEEPALIVE选项
  • Linux socket EAGAIN

    2016-05-13 09:33:33
    Linux - 非阻塞socket编程处理EAGAIN错误 在linux进行非阻塞的socket接收数据时经常出现Resource temporarily unavailable,errno代码为11(EAGAIN),这表明你在非阻塞模式下调用了阻塞操作,在该操作没有完成就返
  • linux socket 异步通知

    2017-01-07 03:46:55
    linux上用socket进行进程间通信,客户端如何在服务器发送数据之后得到通知?现在只能开一个线程循环调用recv函数进行查看吗,有没有一种异步通知的机制啊。 系统linux 语言C++
  • linux socket 状态迁移 源码测试 CLOSE_WAIT 再现
  • linux socket 缓存

    千次阅读 2017-10-10 21:56:09
    问题:同时与多个主机建立连接,如果这些主机同时发生数据到...解答:系统为每个socket建立一个缓存,IP层组包进程在收到数据包后会把数据放入socket缓存。 应用程序通过socket系统调用和远程主机进行通讯,每一个so
  • 使用 #include <errno.h> printf ("errno is: %d\n", errno);会打印数字序列,查表知道那个 2....会打印后面的文字介绍,如Address already in use ... Linux Socket Errno错误代码列表 ...
  • Linux socket 基础API

    千次阅读 2018-09-05 20:21:47
    UNIX/Linux的一个哲学:所有的东西都是文件,socket也不例外,可读。可写,可控制,可关闭的文件描述符。 socket基础API在sys/socket.h 下面的socket系统调用可以创建一个socket。 #include &lt;sys/types.h&...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 39,699
精华内容 15,879
关键字:

linuxsocket

linux 订阅