实验作业二:Java/Linux Socket API

Linux Socket API:

实验作业二:Java/Linux Socket API_第1张图片

 

上图是基于TCP的客户端/服务端模式

 

一些标识:

PF_INET(IP协议族)、AF_INET(IP地址族)、SOCK_STREAM(用于基于流式传输的协议,比如TCP)。

 

一些结构:

struct in_addr:用来表示主机地址,只含有一个域,是 unsigned long in s_addr。

struct sockaddr:

  struct sockaddr {

    u_char  sa_len;

    u_short    sa_family;  // AF_INET

    char    sa_data[14]; // 这里就是14字节的IP地址和端口号

  }

struct sockaddr_in:

  struct sockaddr_in {

    u_char  sin_len;

    u_short    sin_family;      // AF_INET

    u_short  sin_port;        // 端口号,注意必须是网络字节顺序,使用htons函数

    struct     in_addr sin_addr;          // IP地址,注意必须是网络字节顺序,使用htons函数

    char     sin_zero[8];     

  }

 

一些宏:

unsigned long int INADDR_LOOPBACK:表示 "address of this machine",即环回地址,127.0.0.1,也可以是"localhost"。

unsigned long int INADDR_ANY:表示 "any incoming address",当你在设置 struct sockaddr_in 结构体中的变量 sin_addr时可以使用,表示服务器接受所有的连接请求。

unsigned long int INADDR_BROADCAST:表示发送广播报文。

unsigned long int INADDR_NONE:作为返回值,表示某些函数出错了。

 

一些函数:

  位于 'arpa/inet.h'中:

  int inet_aton(const char *name, struct in_addr *addr):将字符串表示的IP地址如'127.0.0.1'转换为二进制数据并保存在addr中,返回非0值表示成功,返回0表示失败。

  unsigned long int inet_addr(const char *name):将字符串表示的IP地址转换为二进制数据后返回,如果输入不合法,会返回INADDR_NONE。

  char *inet_ntoa(struct int_addr addr):将IP地址addr转换为字符串形式并返回

 

一些linux socket函数:

  int socket(int domain, int type, int protocol); domain 是 AF_INET (准确来说在 socket() 中应该使用PF_INET,而在结构体 sockaddr_in 中使用AF_INET);type 选择 SOCK_STREAM,因为我们使用的是TCP协议, protocol 设置为0就好,socket()函数会根据 type 自动选择正确的协议。返回值是 sockfd,即 socket file discriptor,类似于文件描述符。该函数创建了一个socket。

 

  int bind(int sockfd, struct sockaddr *my_addr, int addrlen); sockfd 就是调用 socke 函数的返回值,my_addr 是 struct sockaddr 类型的,但在实际中,我们一般会使用 sockaddr_in 进行设置,然后再强制类型转换。my_addr 主要是设置里面的端口号和IP地址,注意字节顺序的转换即可。 addrlen 我们一般使用 sizeof(struct sockaddr)即可。该函数将一个socket绑定到指定的 ip:port。

 

  int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);这里 serv_addr 是我们要连接到的服务端的地址信息。该函数使得 sockfd 指定的 socket 连接到服务器。

 

  int listen(int sockfd, int backlog);backlog是设定的最大连接数,即一个服务器可以接受多少个连接请求。返回-1表示出错。在调用 listen() 函数之前需要先调用 bind() 函数。

 

  int accept(int sockfd, struct sockaddr *addr, int *addrlen);sockfd 是 listen()ing 中的 socket描述符,addr中存储某个连接的信息,即客户端的IP地址和端口。

 

  int send(int sockfd, const void *msg, int len, int flags); 向指定的socket 发送长度为len的信息,flags 设为0就好。

  int recv(int sockfd, void *buf, int len, unsigned int flags);从指定的socket接收信息并存到 buf 中,len是 buf 的最大长度。

 

  ssize_t write(int fd, const void *buf, size_t count);

  ssize_t read(int fd, void *buf, size_t count);

 

  int close(int sockfd);

 

字节序:

  htons():host to network short

  ntohs():network to host short

  htonl():host to network long

  ntohl():network to host long

  比如,我们有 struct sockaddr_in ina,ina.sin_addr.s_addr = inet_addr("127.0.0.1"),inet_addr()函数会帮我们完成到网络字节序的转变,而 ina.sin_port = htons(12345),就需要调用 htons()函数手动进行转换。

 

一个简单的客户端/服务端通信的例子:

客户端:

实验作业二:Java/Linux Socket API_第2张图片

 

 服务端:

实验作业二:Java/Linux Socket API_第3张图片

 

 实验作业二:Java/Linux Socket API_第4张图片

 

 运行结果:

实验作业二:Java/Linux Socket API_第5张图片

 

基本流程可以将代码对应本文开始给出的那张示意图,对客户端来说就是依次调用 socket()、connect()、write()、read()、close()函数,对服务端来说就是依次调用 socket()、bind()、listen()、accept()、read()、write()、close()函数。

客户端首先创建一个 socket 对象,然后将其连接到一个服务器上(要求服务端必须先于客户端运行),如果连接成功,就可以发送和接受信息。

服务端也是先创建一个 socket 对象,然后调用 bind() 函数将自己绑定到服务端地址,一般就是本机所有可用地址,然后进行阻塞式监听,直到遇到客户端的连接请求,此时会返回一个维护该连接的 socket,服务端通过该新的 socket 与客户端进行通信。

Java Socket API:

在 Java 中,对客户端和服务端进行了区分,一般使用 ServerSocket 创建服务端,使用 Socket 创建客户端。

 

ServerSocket::

一般我常用 ServerSocket serverSocket = new ServerSocket(port);来创建一个服务端的套接字对象,我们看一下该函数的源码:

 

 注意,该构造函数在内部调用了另外一个构造函数,其参数 port 代表端口号,backlog 代表服务器可以接受的最大连接数,bindAddr 代表服务器将使用的IP地址,为null表示本机所有的地址。所以调用该函数其实对应了 Linux Socket API 中的 socket() 、 bind()和 listen()  函数。之后通过调用 serverSocket.accept() 进行阻塞式监听,直到一个连接的到来,然后返回关于该连接的 Socket 对象。所以调用该函数其实对应了 Linux Socket API 中的 accept() 函数。

至于最后的读写我一般是用流操作,即上一步返回的 Socket 对象中的 getInputStream()函数进行读,getOutputStream()函数进行写。

 

Socket():

一般我常用 Socket socket = new Socket(host, port)来创建一个客户端的Socket对象,其中 host 和 port 是服务端的IP地址和端口号。调用该构造函数就对应于 Linux Socket API中的 socket() 和 connect() 函数。

 

一个简单的客户端/服务端通信的例子:

客户端:

实验作业二:Java/Linux Socket API_第6张图片

 

 服务端:

实验作业二:Java/Linux Socket API_第7张图片

 

 运行结果:

实验作业二:Java/Linux Socket API_第8张图片

 

 实验作业二:Java/Linux Socket API_第9张图片

 

 注意应该先启动服务端,再启动客户端。

通过比较使用 Linux Socket API 和 Java Socket API 编写的代码应该就能得到之前提出的对应关系。

 

  

 

 

 

 

 

 

 

 

你可能感兴趣的:(实验作业二:Java/Linux Socket API)