linux java socket_实验作业二:Java/Linux Socket API

Linux Socket API:

9415f3b246eba63ece458e5772902ef6.png

上图是基于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()函数手动进行转换。

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

客户端:

51f5ab88e343a77cc7a954e29e53fdbf.png

服务端:

c2604c3ecd09958edd8ea76ef69806c4.png

2c7aa0b9168d1d6d07af47a7b524e318.png

运行结果:

775936116885afa6c14dcc03dbd78661.png

基本流程可以将代码对应本文开始给出的那张示意图,对客户端来说就是依次调用 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);来创建一个服务端的套接字对象,我们看一下该函数的源码:

3d6acd8210229b9fc7d0e09f12f1c40f.png

注意,该构造函数在内部调用了另外一个构造函数,其参数 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() 函数。

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

客户端:

da012e817c251e312ba3c987837bec81.png

服务端:

ff32ececa0060cb45d60e179019724f3.png

运行结果:

bec4cc4c7b236973effe7603a87b0c55.png

d2c11d737eb98b818b41e2432f5e9afe.png

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

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值