从零开始的Socket编程 一

从零开始的Socket编程 一


使用Socket

如前文所述,Socket用于在网络上连接两个不同的进程,将这个两个经常分别称为Server和Client,Server就像是插座后面的电源,等待者插头,也就是Client来进行连接。只有当Server和Client成功建立起连接,这时两个进程才可以进行通信。

由于两个进程扮演了不同的角色,需要提供不同的功能,因此Server和Client创建Socket的方式也不同。
在这里插入图片描述

从图中可以图中可以看出要使用socket进行通信的基本流程。

一个套接字接口构成一个连接的一端,而一个连接可完全由一对套接字接口规定

要构成一个连接需要两个socket,其中一个先创建并一直等待,称为server; 另一个在需要建立连接的时候主动发起连接,称为client

server端通过socket()函数创建一个socket,然后调用bind()告知socket应该在哪个地址上进行监听,之后再调用listen()启动监听, 在默认没开启非阻塞模式的情况下,accept()会等到接受到一个来自client的连接请求后返回一个新socket.至此,server的准备工作完成,在调用accept()后,server会阻塞直到由连接进来.

server端建立好socket后,需要client主动发起连接后才能构成一个可通信的连接.client端同样也需要调用socket()创建一个socket, 但是不需要调用bind(), listen(), accept()来设置监听地址和等待连接.

在Linux/Unix下,Socket被看作时文件,因此接下里的通信操作类似与文件IO,通过read()write()来完成,只不过socket会提供其他的函数来提供更多的功能. 在通信结束后,类似与文件读写完成后,需要调用close()来关闭socket.

Socket 的基本操作

在上文的图中也包括了socket的基本操作,以及这些操作在socket通信过程中所处的位置。

socket

int socket (int __domain, int __type, int __protocol)
该函数创建一个socket描述符(socket descriptor),该描述符唯一标识一个socket,在Linux下该接口的声明为:

/* 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) __THROW;
参数
  1. __domain : 协议域,又称协议族,定义在"sys/socket.h"(Windows系统在winsock2.h)文件中,常用的协议族有:AF_INET(IPv4),AF_INET6(IPv6),AF_LOCAL(用于同一台主机上的进程间通信,详细可参考https://blog.csdn.net/frank_jb/article/details/77199834)等。协议族指定了socket的地址类型,在通信中必须采用对应的地址。
  2. __type : 指定socket的类型。常用的有SOCK_STREAM——面向连接的,常用于TCP,SOCK_DGRAM——面向报文的,常用于UDP等。这些类型定义在socket_type.h文件中
  3. __protocol : 指定协议。常用协议有IPPROTO_TCP——TCP协议, IPPROTO_UDP ——UDP协议,IPPROTO_TIPC——TIPC(Transparent Inter Process Communication)协议

上述type和protocol不可以自由组合,当protocol为0时会根据type选择默认的协议。

返回值

返回新创建的socket描述符,若创建失败则返回-1

bind

对于server端进程而言,在创建socket后需要将协议族中一个特定的地址赋予socket,在改地址上等待client的连接

/* Give the socket FD the local address ADDR (which is LEN bytes long).  */
extern int bind (int __fd, __CONST_SOCKADDR_ARG __addr, socklen_t __len)
  1. __fd : 文件描述符,即socket描述符,调用socket()的返回值
  2. __addr : #define __CONST_SOCKADDR_ARG const struct sockaddr *,指定要绑定的地址,该结构根据创建socket时的地址协议族的不同而不同。如IPv4对应的是
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;
};

而IPv6对应的是:

#if !__USE_KERNEL_IPV6_DEFS
/* Ditto, for IPv6.  */
struct sockaddr_in6
{
    __SOCKADDR_COMMON (sin6_);
    in_port_t sin6_port;	/* Transport layer port # */
    uint32_t sin6_flowinfo;	/* IPv6 flow information */
    struct in6_addr sin6_addr;	/* IPv6 address */
    uint32_t sin6_scope_id;	/* IPv6 scope-id */
};
#endif /* !__USE_KERNEL_IPV6_DEFS */

#if !__USE_KERNEL_IPV6_DEFS
/* IPv6 address */
struct in6_addr
{
    union
    {
    uint8_t	__u6_addr8[16];
    uint16_t __u6_addr16[8];
    uint32_t __u6_addr32[4];
    } __in6_u;
#define s6_addr			__in6_u.__u6_addr8
#ifdef __USE_MISC
# define s6_addr16		__in6_u.__u6_addr16
# define s6_addr32		__in6_u.__u6_addr32
#endif
};

#endif /* !__USE_KERNEL_IPV6_DEFS */
  1. __len :对应的地址的长度

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;

server调用listen来监听socket。socket创建的socket默认为主动发起请求的,listen使得socket变为被动接收请求,等待连接的到来。

  1. __fd : socket描述符
  2. __n : socket可以排队的最多的连接数,超过该值后的连接将被拒绝。

成功时返回0,失败返回-1

accept

/* 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);

等待连接到达,当连接到达后,建立一个新的socket用于通信。

  1. __fd : 服务端的socket描述符
  2. __addr : 指向struct sockaddr的指针,输出参数,用于返回发起连接的客户端地址
  3. __addr_len : 地址长度

成功时返回新的socket描述符,失败时返回-1

需要区分socket返回的描述符和accept返回的描述符。
socket返回的socket称为监听socket描述符,一个服务器通常只有一个监听socket描述符,在该服务器的生命周期内一直存在;
accept返回的描述符是由操作系统为每个服务器进程接收的客户连接创建的socket描述符,当结束服务端与客户端的一次会话后对应socket描述符将被关闭。

connect

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);
  1. __fd : 客户端通过socket()创建的socket描述符
  2. __addr : 要连接的地址的服务端的socket地址
  3. __len : __addr的长度

read/write

使用上述函数即可建立起网络连接,连接建立后即可进行通信,提供了多组函数进行通信:

//unistd.h>
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
//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);

readwrite是Linux上通用的文件IO函数,之前也说了,在Linux将Socket也看作是文件。
recv, recvfrom, recvmsg都是用来接收数据的,在Linux上可以通过man recvfrom查看三者的具体差别
send, sendto, sendmsg又来发送数据,可通过man sendmsg查看具体信息
总的来说sendmsg/recvmsg能提供最多的信息

总结

本文先介绍了socket通信的基本流程,然后介绍了socket通信相关的接口.熟悉编程的同学可能已经可以据此实现简单的socket通信了,由于不希望一文的篇幅过长因此将实现通信的代码放在下一篇文章中

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值