网络通信

相关概念

网络
(1) 概念:连接终端系统(主机)的通信系统
(2) 功能:实现资源共享、通信

  • 局域网(Local Area Network,LAN),就是同一区域内通过一定形式连接起来的计算机。
  • 广域网(Wide Area Network,WAN),由LAN延伸到更大的范围。
  • 因特网(Internet),由无数的LAN和WAN组成。
  • 服务器是指提供信息的计算机或程序客户机是指请求信息的计算机或程序,而网络用于连接服务器与客户机,实现两者相互通信。
  • IP地址:IP地址是计算机在互联网中唯一标识
  • 端口:区分应用程序,实现程序间的通信
  • 假如一台计算机提供了HTTP、FTP等多种服务,那么客户机通过不同的端口来确定连接到服务器的哪项服务上。

说明: 0~1023端口用于知名网络服务和应用,1024以上的端口用于用户的普通应用程序。

套接字(Socket)

(1)功能:连接应用程序端口(Port)
(2) 客户端与服务器通过套接字建立连接通信
UDP套接字:不可靠。
TCP/IP套接字

  • 可靠的双向流协议;
  • 可发送任意数量的数据;
  • 提供消息确认、错误检测、错误恢复等服务。

Java将套接字抽象化为类,程序设计者只需创建Socket类对象,即可使用套接字。

C/S模式

客户端/服务器模式(c/s)
①客户端:请求服务的计算机。
②服务器:提供服务的计算机。

  • 多个客户端可同时访问服务器。
  • 一台服务器可以同时为数千台客户端服务


java.net包

(1) InetAddress类

  • 封装IP地址,提供与IP地址相关的方法。
  • InetAddress方法会抛出UnknownHostException异常,在主机不存在或网络连接错误时发生。
InetAddress类常用方法 说明
InetAddress getLocalHost( ) 创建本地主机的InetAddress对象(含主机名+IP地址)
String getHostName( ) 如果是本机则返回计算机名,不是本机则返回主机名,没有域名则是IP地址。
String getHostAddress( ) 返回IP地址
InetAddress getByName( String host) 已知主机名,返回主机的InetAddress对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package net;
import java.net.*;
public class LoaclIPTest {

public static void main(String[] args) {
// TODO Auto-generated method stub
InetAddress ip;
try {
//创建本地主机的InetAddress对象(含主机名+IP地址)
ip=InetAddress.getLocalHost();
String localname=ip.getHostName(); //获取本机名
String localip=ip.getHostAddress(); //获取IP地址
//已知主机名,返回主机的InetAddress对象
//InetAddress aa=InetAddress.getByName("SC-201611191816");
System.out.println("本机名:" + localname);
System.out.println("本机IP地址:" + localip);
System.out.println("本机InetAddress对象:" + ip);
// System.out.println("aa:" + aa);

}catch(UnknownHostException e) {
e.printStackTrace();
}
}

}

(2) ServerSocket

  • 用于创建TCP服务器端Socket,使服务器绑定一个端口,等待客户的连接请求。
ServerSocket常用方法 说明
构造方法:ServerSocket(int port) 创建绑定到特定端口的服务器套接字
Socket accept() 等待客户端的连接,有客户端连接返回一个对应的Socket
InetAddress getInetAddress() 返回封装了ServerSocket绑定的IP地址的InetAddress对象
boolean isClosed() 判断ServerSocket是否关闭
void bind(SocketAddress endpoint) ServerSocket对象绑定到指定的IP地址和端口号
int getInetAddress() 返回服务器套接字等待的端口号
void close() 关闭套接字
boolean isBound() 判断ServerSocket的绑定状态

(3) Socket

  • 创建TCP客户端Socket
Socket常用方法 说明
Socket(String hostName,int port) 根据主机名和端口创建Socket
Socket(InetAddress a,int port) 根据InetAddress和端口创建Socket
InputStream getInputStream() 返回InputStream类型的输入流对象
OutputStream getOutputStream() 返回OutputStream类型的输出流对象
int getPort() 返回Socket对象与服务器端连接端口号
InetAddress getLocalAddress() 获取Socket对象绑定的本地IP地址
void close() 关闭Socket连接,结束本次通信

编写C/S程序

服务器端

  • 创建一个服务器套接字(ServerSocket),绑定到指定的端口
  • 调用accept()方法,侦听来自客户端的请求,如果客户发出请求,则接受连接,返回通信套接字(Socket)。
  • 调用Socket的getInputStreamgetOutpuStream方法,获得I/O流,开始网络数据的接收发送
  • 关闭通信套接字,关闭服务器套接字

客户端

  • 创建一个套接字(Socket),向服务器的侦听端口发出请求。
  • 与服务器正确连接后,调用Socket类getInputStream()getOutputStream()方法,获得I/O流,开始网络数据的接收和发送。
  • 关闭通信套接字

Socket通信方式

利用Socket方式进行数据通信与传输的整个过程如图所示:Socket对象代表主叫方ServerSocket对象代表被叫方,执行accept()方法表示同意建立连接。连接一旦建立,会自动创建一个输入流和一个输出流,通过这两个流可以实现数据的发送。

Socket编程思路

(1)I/O流

  • 计算机系统将除CPU内存以外其他设备都当作文件处理,包括网络终端(网卡)也不例外。
  • 两台主机之间通过Socket建立握手连接之后,主要通过I/O流接收发送数据,实际上,绝大部分网络操作是在操作I/O流
  • 操作网络端口流与操作磁盘文件流原理相似。
  • 出于效率的考虑,Socket使用的是字节流,但是在实际操作中,字节往往会带来不便,所以我们一般会把字节流改造成字符流后进行操作。

(2)输入

  • 先利用InputStreamReaderInputStream转化成字符流
  • 然后再使用BufferedReader建立缓冲区,以提高效率。
    1
    2
    3
    4
    5
    InputStream is=socket.getInputStream();
    InputStreamReader isr=new InputStreamReader(is);
    BufferedReader in=new BufferedReader(isr);
    in.readLine(); //从流中读出信息

(3)输出

  • 利用PrintWriter类对OutputStream进行包装
  • PrintWriter称为打印输出流,包装类,它可以直接输出各种类型的数据。
    如:void println( String ) ;
    1
    2
    3
    OutputStream os=socket.getOutputStream();
    PrintWriter pw=new PrintWriter(os, true);
    pw.println(“待写信息”); //将信息写入流

C/S实例

Client客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
package 网络通信;
import java.io.*;
import java.net.*;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class Client implements ActionListener {
JFrame f;
JPanel p;
JTextArea jt; //多行文本域
JTextField t; //单行文本域
JButton b;
JScrollPane jsp;//滚动条
PrintWriter pw; //防止局部变量
public Client() {
f=new JFrame("客户端窗口");
p=new JPanel();
jt=new JTextArea();
t=new JTextField(20);
b=new JButton("发送");
b.addActionListener(this);
f.add(p);
p.add(jt);
p.add(t);
p.add(b);
jsp=new JScrollPane(jt); //文本域加滚动条
f.add(jsp);
f.setSize(350,250);
f.setLocation(300, 200);
f.add(p,BorderLayout.SOUTH);
f.setIconImage((new ImageIcon("MyImage/qq图标.jpg")).getImage());
f.setResizable(false);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
/*System.exit(0)是退出整个程序,如果有多个窗口,全部都销毁退出。
frame.setDefaultCloseOperation()是设置用户在此窗体上发起"close"时默认执行的操作。必须指定以下选项之一:
(1)DO_NOTHING_ON_CLOSE(在WindowConstants中定义):不执行任何操作,要求程序在已注册的 WindowListener 对象的 windowClosing 方法中处理该操作。
(2)HIDE_ON_CLOSE(在WindowConstants中定义):调用任意已注册的WindowListener对象后自动隐藏该窗体。
(3)DISPOSE_ON_CLOSE(在WindowConstants中定义):调用任意已注册WindowListener的对象后自动隐藏并释放该窗体。
(4)EXIT_ON_CLOSE(在JFrame中定义):使用System exit方法退出应用程序。仅在应用程序中使用。
默认情况下,该值被设置为 HIDE_ON_CLOSE
也就是说没有设置的话,默认点关闭时只是隐藏窗体,在后台进程中还可以看到,如果有多个窗口,只是销毁调用dispose的窗口,其他窗口仍然存在,整个应用程序还是处于运行状态。*/
f.setVisible(true);



//客户端程序思路
//创建一个套接字(Socket),向服务器的侦听端口发出请求。
//与服务器正确连接后,调用Socket类的getInputStream()(接受)和getOutputStream()(发送)方法,获得I/O流,开始网络数据的接收和发送。
//关闭通信套接字。




try {
Socket s=new Socket("127.0.0.1",8998); //进入端口,参数为服务器IP地址和端口 可通过cmd命令查找 ping和netstat -an
//尝试建立与服务器的连接。如果成功,则返回Socket对象。

//向服务器发送信息
pw=new PrintWriter(s.getOutputStream(),true);


//读取键盘控制台的内容
InputStreamReader isr=new InputStreamReader(System.in);
BufferedReader br=new BufferedReader(isr);

//接收服务器的内容
InputStreamReader isr1=new InputStreamReader(s.getInputStream());
BufferedReader br1=new BufferedReader(isr1);

//死循环 多次交流
while(true) {
/*System.out.println("客户端说:");
String str=br.readLine(); //接受控制台
pw.println(str);//向服务器发送信息
*/

String str1=br1.readLine();//接受服务器的信息
jt.append("服务器对我说说:"+str1+"\r\n"); //在多行文本域显示
}

//s.close();//关闭头接字流
}catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
// TODO 自动生成的方法存根
Client c=new Client();
new Client();
}
@Override
public void actionPerformed(ActionEvent e) {
// TODO 自动生成的方法存根
if(e.getSource()==b) {
String str=t.getText();
jt.append("我对服务器说:"+str+"\r\n"); //append追加 不覆盖前面的内容
//setText与append区别 在于不会覆盖之前的信息
pw.println(str);//发送给客户端
t.setText("");//每次发完单行文本框清空
}
}
}

Server服务端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
package 网络通信;
import java.net.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
//尝试写好窗口 继承实现
import javax.swing.*;
import java.io.*;
public class Server implements ActionListener {
JFrame f;
JPanel p;
JTextArea jt; //多行文本域
JTextField t; //单行
JButton b;
JScrollPane jsp;//滚动条
PrintWriter pw; //消除局部变量
public Server(){
f=new JFrame("服务器窗口");
p=new JPanel();
jt=new JTextArea();
t=new JTextField(20);
b=new JButton("发送");
b.addActionListener(this);
f.add(p);

p.add(jt);
p.add(t);
p.add(b);
jsp=new JScrollPane(jt); //文本域加滚动条
f.add(jsp);
f.setSize(350,250);
f.setLocation(300, 600);
f.add(p,BorderLayout.SOUTH);
f.setIconImage(new ImageIcon("MyImage/qq图标.jpg").getImage());
f.setResizable(false);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
/*System.exit(0)是退出整个程序,如果有多个窗口,全部都销毁退出。
frame.setDefaultCloseOperation()是设置用户在此窗体上发起"close"时默认执行的操作。必须指定以下选项之一:
(1)DO_NOTHING_ON_CLOSE(在WindowConstants中定义):不执行任何操作,要求程序在已注册的 WindowListener 对象的 windowClosing 方法中处理该操作。
(2)HIDE_ON_CLOSE(在WindowConstants中定义):调用任意已注册的WindowListener对象后自动隐藏该窗体。
(3)DISPOSE_ON_CLOSE(在WindowConstants中定义):调用任意已注册WindowListener的对象后自动隐藏并释放该窗体。
(4)EXIT_ON_CLOSE(在JFrame中定义):使用System exit方法退出应用程序。仅在应用程序中使用。
默认情况下,该值被设置为 HIDE_ON_CLOSE
也就是说没有设置的话,默认点关闭时只是隐藏窗体,在后台进程中还可以看到,如果有多个窗口,只是销毁调用dispose的窗口,其他窗口仍然存在,整个应用程序还是处于运行状态。*/
f.setVisible(true);


/* 查找IP地址方法
InetAddress ip;
try {
ip = InetAddress.getLocalHost();
//创建本地主机的InetAddress对象(含主机名+IP地址)
String localname = ip.getHostName(); // 获取本机名
String localip = ip.getHostAddress(); // 获取IP地址
InetAddress aa= InetAddress.getByName("SC-201611191816");
//已知主机名,返回主机的InetAddress对象
System.out.println("本机名:" + localname);
System.out.println("本机IP地址:" + localip);
System.out.println("本机InetAddress对象:" + ip);
System.out.println("aa:" + aa);

} catch (UnknownHostException e1) {
// TODO 自动生成的 catch 块
e1.printStackTrace();
}

*/
//服务端程序思路
//创建一个服务器套接字(ServerSocket),绑定到指定的端口。
//调用accept()方法,侦听来自客户端的请求,如果客户发出请求,则接受连接,返回通信套接字(Socket)。
//InputStreamReader getInputStream BufferedReader 读取客户端和控制台
//调用Socket的getInputStream(读取)和getOutpuStream(发送)方法,获得I/O流,开始网络数据的接收和发送
//PrintWriter getOutputStream方法发送
//关闭通信套接字,关闭服务器套接字。




try {
ServerSocket ss=new ServerSocket(8998);
//ServerSocket在服务器上定义一个接口 方便客户端连接
System.out.println("服务器正在监听");
Socket so=ss.accept();
//等待客户机的链接,若链接,则创建一个Socket类型的套接字
//套接字:连接应用程序和端口 客户端与服务器通过套接字建立连接和通信

//读取客户端发来的信息
//先利用InputStreamReader将InputStream转化成字符流。
//然后再使用BufferedReader建立缓冲区,以提高效率。
//readLine()方法读取
InputStreamReader isr=new InputStreamReader(so.getInputStream());
BufferedReader br=new BufferedReader(isr);


//给客户端发信息
//利用PrintWriter类对OutputStream进行包装
//PrintWriter称为打印输出流,包装类,它可以直接输出各种类型的数据。
//如:void println( String ) ;
pw = new PrintWriter(so.getOutputStream(),true);

//读取服务器控制台发来的信息
/*
InputStreamReader isr1=new InputStreamReader(System.in);
BufferedReader br1=new BufferedReader(isr1);
*/
while(true) {
String str=br.readLine(); //接受客户端的信息
jt.append("客户端对我说说:"+str+"\r\n"); //在多行文本域显示
/*System.out.println("服务器说:");
String str1=br1.readLine();
pw.println(str1); //输出服务器发送的信息 */
}
//so.close();//关闭套接字流 结束点红点 再点双叉防止下次运行 端口被占用
}catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
// TODO 自动生成的方法存根
Server s=new Server();
new Server();
}
@Override

//监听
public void actionPerformed(ActionEvent e) {
// TODO 自动生成的方法存根
if(e.getSource()==b) { //是否点击按钮发送
String str=t.getText(); //从单行文本域中取出1来
jt.append("我对客户端说:"+str+"\r\n"); //append追加 不覆盖前面的内容 发送到多行文本域
//setText与append区别 在于不会覆盖之前的信息
pw.println(str); //发送给客户端
t.setText(""); //每次发完单行文本框清空
}
}

}