我们之所以可以上网,就是因为我们请求远程服务器上面的信息,然后服务器将相应的信息发送给了我们,在一个局域网之内,大家可以通过飞秋联系并且发送文件,这些都和今天要写的这篇文章有关,那就是网络编程,当然,中间主要介绍的还是Java中的网络编程是如何实现的。
1.传输协议
在介绍网络编程的时候,需要先介绍一下传输协议,传输协议就是通信双方事先约定好彼此以何种方式传输数据,当然,不同的传输协议所传输数据的格式也是不一样的,会根据它们具体的特点而有所不同,这里我们不用了解的那么深入,只要知道一下就好了,这里想说的两种传输协议就是: UDP
和 TCP
。
UDP
协议对于通信双方是无需连接的,发送方直接将数据发送给接收方,而不用管接收方是否接收到,很显然,这样传输速度快,但是却无法保证数据的完整性,我们用QQ进行视频通话时,就是使用的 UDP
协议,通话时视频数据即使有丢失,只要不是丢失的很多,对我们正常通话都不会有很大影响,因为我们视频时部分视频画面没有实时显示也没有多大关系。
TCP
协议对于通信双方则是要求有连接的,双方必须通过”三次握手”来确认连接已连通,之后才会发送数据,在发送数据的过程中,如果有部分数据的丢失,TCP
也会要求将接收方未收到的数据重新发送,直到所有的数据接受完成,很显然,这种协议可以保证数据的完整性,但很显然效率不是很高。
传输协议:
UDP:发送数据速度快,但数据完整性不能保证
TCP:能够保证数据的完整性,但发送数据的速度不快
2.网络通信的三要素
当我们的服务器端和客户端进行数据交互的时候,必须要满足哪些必要条件呢?第一,就是要知道彼此在哪一台主机上面,只有这样才能知道将数据发送到哪里给彼此;第二,知道了应该将数据发送到哪台主机上面之后,但是像我们的电脑,会有QQ、Chrome、迅雷,还有酷狗这么多的软件应用,那怎么知道将数据发送到哪一个应用呢?这里要说明的就是每一个程序应用都会占用一个端口号,想和这个应用进行数据交换,必须要通过这个端口号,所以第二点就是端口号了;第三,那通信双方会采取怎样的方式收发数据呢?如果彼此的对应方式不匹配,那就算接受到了数据也是不会明白其中的含义的啊,所以第三点那就是协议了。
网络通信的三要素:
1.IP地址
2.端口号
3.通信协议
可以将上面的交换数据过程对比为我们生活中的相互发送快递,发送快递,我们必须要知道接受者的地址,那这就是IP地址了,知道了地址之后,想要送上门那就必须要知道门牌号了,这就相当于是端口号,最后一个,就是发送快递时会选择哪种快递商,只有事先明确了会发送哪种快递,才能接受对应的快件啊,这就是协议了。
2.1 Java中表示IP地址
Java中要表示IP地址的话可以使用 InetAddress
这个类,要获得这个类的类对象可以使用 public static InetAddress getByName(String host)
方法,这里既可以传入主机名,也可以传入IP地址。
InetAddress address = InetAddress.getByName("WIN-SNN7GVNNB0F") 通过主机名去得到IP地址
InetAddress address = InetAddress.getByName("127.0.0.1");//ip地址是唯一的
这两种方法都可以获得IP地址,127.0.0.1
代表的是本机IP地址。
3.UPD协议实现
下面写一段程序来说明使用 UDP
协议是如何进行数据发送和接受的,需要注意的是,如果想自己运行下面的代码,需要自己根据实际情况修改IP地址和端口号。
发送端:
public class UdpSendDemo {
public static void main(String[] args) throws IOException {
DatagramSocket socket = new DatagramSocket();
String info = "study hard";
byte[] bys = info.getBytes();
int length = bys.length;
InetAddress toAddress = InetAddress.getByName("127.0.0.1");
int port = 9022;
DatagramPacket dp = new DatagramPacket(bys, length, toAddress, port);
socket.send(dp);
socket.close();
}
}
接收端:
public class UdpReceiveDemo {
public static void main(String[] args) throws IOException {
DatagramSocket receiveSocket = new DatagramSocket(9022);
byte[] bys = new byte[1024];
DatagramPacket dp = new DatagramPacket(bys, bys.length);
receiveSocket.receive(dp);
InetAddress sendAddress = dp.getAddress();
byte[] data = new byte[1024];
data = dp.getData();
int length = dp.getLength();
System.out.println("sender:" + sendAddress);
System.out.println("data:" + (new String(data, 0, length)));
receiveSocket.close();
System.out.println("--------");
System.out.println(new String(bys, 0, length));
}
}
运行程序的时候需要先运行接收端等待接受数据,再接受发送端发送数据,不然如果先运行发送端的话,由于 UDP
是无连接的协议,发送端会直接发送数据,而不管接收端收到了没有,那样我们就得不到预想的效果了。
4.TCP协议实现
4.1 使用TCP进行收发数据
先像上面UDP的例子一样,实现一个最简单的功能,实现基本的数据收发。
客户端:
public class TcpClientDemo {
public static void main(String[] args) throws IOException {
Socket s = new Socket(InetAddress.getByName("127.0.0.1"),9046);
OutputStream os = s.getOutputStream();
String data = "something is coming!";
byte[] bys = data.getBytes();
os.write(bys);
os.close();
s.close();
}
}
服务端:
public class TcpServerDemo {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(9046);
Socket s = ss.accept();
InetAddress sendAddress = s.getInetAddress();
InputStream is = s.getInputStream();
byte[] bys = new byte[1024];
int len;
len = is.read(bys);
System.out.println(sendAddress.getHostName());
System.out.println(sendAddress);
System.out.println(new String(bys, 0, len));
s.close();
ss.close();
}
}
还是需要提醒一句,程序中的IP地址和端口号都要根据自己的实际情况进行修改,否则可能运行不成功,其中端口号的选择范围可以是0到65535,但是前面0到1023范围的端口号属于系统可能已经在使用的端口号,因此我们在使用的时候,最好选择其它的,这样就不会因为冲突而发生错误。
4.2 用TCP协议发送数据并将接受到的数据转换成大写并且返回
客户端:
public class TcpClientDemo2 {
public static void main(String[] args) throws IOException {
Socket s = new Socket(InetAddress.getByName("127.0.0.1"), 8888);
OutputStream os = s.getOutputStream();
String data = "we are here!";
byte[] bys = data.getBytes();
os.write(bys);
InputStream is = s.getInputStream();
int len = is.read(bys);
String backStr = new String(bys,0,len);
System.out.println("backfeed:"+backStr);
s.close();
}
}
服务端:
public class TcpServerDemo2 {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(9056);
Socket s = ss.accept();
InputStream is = s.getInputStream();
byte[] bys = new byte[1024];
int len = is.read(bys);
String str = new String(bys, 0, len);
System.out.println(str);
String newStr = str.toUpperCase();
bys = newStr.getBytes();
OutputStream os = s.getOutputStream();
os.write(bys);
s.close();
ss.close();
}
}
4.3 模拟用户登录
首先先确定学生类:
public class Student {
private String username;
private String password;
public Student() {
super();
}
public Student(String username, String password) {
super();
this.username = username;
this.password = password;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((password == null) ? 0 : password.hashCode());
result = prime * result
+ ((username == null) ? 0 : username.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Student other = (Student) obj;
if (password == null) {
if (other.password != null)
return false;
} else if (!password.equals(other.password))
return false;
if (username == null) {
if (other.username != null)
return false;
} else if (!username.equals(other.username))
return false;
return true;
}
}
在程序运行的时候先预设几个学生对象,作为以后验证登陆的数据:
public class StudentDB {
private static List<Student> list = new ArrayList<Student>();
static{
list.add(new Student("卡卡西","绝版天堂"));
list.add(new Student("带土","轻轻风铃"));
list.add(new Student("琳","暗夜烤鱼"));
list.add(new Student("水门","黄色闪光"));
}
public static List<Student> getUsers(){
return list;
}
}
接下来才是客户端与服务端的程序:
客户端:
public class TcpClientDemo3 {
public static void main(String[] args) throws IOException {
//建立与服务器的连接
Socket s = new Socket(InetAddress.getByName("127.0.0.1"), 9021);
//由用户输入用户名和密码进行登录
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
System.out.print("请输入用户名:");
String username = br.readLine();
System.out.print("请输入密码:");
String password = br.readLine();
//向服务器写数据
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
s.getOutputStream()));
bw.write(username);
bw.newLine();
bw.flush();
bw.write(password);
bw.newLine();
bw.flush();
//接受服务器传来的数据
BufferedReader br2 = new BufferedReader(new InputStreamReader(
s.getInputStream()));
String result = br2.readLine();
System.out.println(result);
s.close();
}
}
服务端:
public class TcpServerDemo3 {
public static void main(String[] args) throws IOException {
// 监听端口,建立与客户端的连接
ServerSocket ss = new ServerSocket(9021);
Socket s = ss.accept();
// 接受客户端的数据
BufferedReader br = new BufferedReader(new InputStreamReader(
s.getInputStream()));
String username = br.readLine();
String password = br.readLine();
// 验证登录
boolean flag = false;
// if ("admin".equals(username) && "123".equals(password)) {
// flag = true;
// }
List<Student> users = StudentDB.getUsers();
Student stu = new Student(username, password);
if (users.contains(stu)) {
flag = true;
}
String result = null;
if (flag) {
result = "登陆成功";
} else {
result = "登录失败";
}
// 向客户端写数据
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
s.getOutputStream()));
bw.write(result);
bw.newLine();
bw.flush();
s.close();
ss.close();
}
}
其实之前都是将用户名和密码写死在了程序中,只有用户名是 admin
,密码是 123
的才能登陆成功,但是感觉太单调了,像这样加入几个学生对象更有逻辑一点。
5.几个有趣的小例子
5.1 猜数字小游戏
客户端输入用户猜的数字,并将数字发送给服务端,服务端进行验证并将验证结果返回给客户端,在给定的次数中,如果用户能够猜出数字那就代表用户赢了,没有的话那就输了,如果次数到了程序也就停止运行了。
客户端:
public class Client3 {
public static void main(String[] args) throws IOException {
Socket s = new Socket(InetAddress.getByName("127.0.0.1"), 9074);
while (true) {
BufferedReader br = new BufferedReader(new InputStreamReader(
System.in));
System.out.print("请输入一个0到100之间的数字:");
String guess = br.readLine();
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
s.getOutputStream()));
bw.write(guess);
bw.newLine();
bw.flush();
BufferedReader br2 = new BufferedReader(new InputStreamReader(
s.getInputStream()));
String result = br2.readLine();
System.out.println(result);
if(result.equals("GAME OVER") || result.equals("YOU ARE WIN")){
break;
}
}
s.close();
}
}
服务端:
public class Server3 {
public static void main(String[] args) throws IOException {
Random r = new Random();
int num = r.nextInt(100) + 1;
ServerSocket ss = new ServerSocket(9074);
Socket s = ss.accept();
int count = 0;
while (true) {
BufferedReader br = new BufferedReader(new InputStreamReader(
s.getInputStream()));
String guess = br.readLine();
int guessNum = Integer.parseInt(guess);
String result = null;
if (count > 5) {
result = "GAME OVER";
} else {
if (guessNum > num) {
result = "大了";
} else if (guessNum < num) {
result = "小了";
} else if (guessNum == num) {
result = "YOU ARE WIN";
}
}
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
s.getOutputStream()));
if (result.equals("GAME OVER") || result.equals("YOU ARE WIN")) {
bw.write(result);
bw.newLine();
bw.flush();
break;
} else {
bw.write(result);
bw.newLine();
bw.flush();
}
count++;
}
s.close();
ss.close();
}
}
5.2 用户登录完善版
新的要求:
1.用户数据应该保存到文本文件当中 user.txt
2.登录操作的过程应该写入到日志文本当中 log.txt
格式为:账号$$密码$$登录成功$$时间
程序就放在这下面吧!
user.txt
卡卡西##绝版天堂
带土##轻轻风铃
琳##暗夜烤鱼
水门##黄色闪光
登录会产生的日志文本文件 log.txt
鸣人$$雏田$$登录失败$$2017年06月21日21:20
佐助$$小樱$$登录失败$$2017年06月21日21:20
带土$$轻轻风铃$$登陆成功$$2017年06月21日21:38
定义的学生类:
public class Student {
private String username;
private String password;
public Student() {
super();
}
public Student(String username, String password) {
super();
this.username = username;
this.password = password;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((password == null) ? 0 : password.hashCode());
result = prime * result
+ ((username == null) ? 0 : username.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Student other = (Student) obj;
if (password == null) {
if (other.password != null)
return false;
} else if (!password.equals(other.password))
return false;
if (username == null) {
if (other.username != null)
return false;
} else if (!username.equals(other.username))
return false;
return true;
}
@Override
public String toString() {
return "Student [username=" + username + ", password=" + password + "]";
}
}
获得学生数据的数据类:
public class StudentDB {
static List<Student> users = new ArrayList<Student>();
static {
try {
readInfo();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
getUsers();
}
public static void readInfo() throws IOException {
BufferedReader br = new BufferedReader(new FileReader(
"src/day12/practice/user.txt"));
String line = null;
while ((line = br.readLine()) != null) {
String[] infos = line.split("##");
Student s = new Student(infos[0], infos[1]);
users.add(s);
}
br.close();
}
public static List<Student> getUsers() {
return users;
}
}
读写日志信息的工具类:
public class Utils {
static List<String> arr = new ArrayList<String>();
public static void main(String[] args) throws IOException {
getInfo();
}
public static void writeInfo() throws IOException{
BufferedWriter bw = new BufferedWriter( new FileWriter("src/day12/practice/log.txt"));
for (int i = 0; i < arr.size(); i++) {
String line = arr.get(i);
bw.write(line);
bw.newLine();
bw.flush();
}
bw.close();
}
public static void readInfo() throws IOException{
BufferedReader br = new BufferedReader(new FileReader(
"src/day12/practice/log.txt"));
String line;
while((line=br.readLine())!=null){
arr.add(line);
}
br.close();
}
public static List<String> getInfo() throws IOException{
readInfo();
System.out.println(arr);
return arr;
}
}
客户端:
public class Client4 {
public static void main(String[] args) throws IOException {
Socket s = new Socket(InetAddress.getByName("127.0.0.1"), 8078);
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
System.out.print("请输入用户名:");
String username = br.readLine();
System.out.print("请输入密码:");
String password = br.readLine();
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
s.getOutputStream()));
bw.write(username);
bw.newLine();
bw.flush();
bw.write(password);
bw.newLine();
bw.flush();
BufferedReader br2 = new BufferedReader(new InputStreamReader(
s.getInputStream()));
String result = br2.readLine();
System.out.println(result);
s.close();
}
}
服务端:
public class Server4 {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(8078);
Socket s = ss.accept();
BufferedReader br = new BufferedReader(new InputStreamReader(
s.getInputStream()));
String username = br.readLine();
String password = br.readLine();
List<Student> users = StudentDB.getUsers();
Student stu = new Student(username, password);
boolean flag = false;
if (users.contains(stu)) {
flag = true;
}
String result = null;
if (flag) {
result = "登陆成功";
} else {
result = "登录失败";
}
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日HH:mm");
Date d = new Date();
String str = sdf.format(d);
List<String> infos = Utils.getInfo();
StringBuilder sb = new StringBuilder();
sb.append(username);
sb.append("$$");
sb.append(password);
sb.append("$$");
sb.append(result);
sb.append("$$");
sb.append(str);
System.out.println(sb.toString());
infos.add(sb.toString());
Utils.writeInfo();
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
s.getOutputStream()));
bw.write(result);
bw.newLine();
bw.flush();
s.close();
ss.close();
}
}
6.总结
这篇文章算是对网络编程学习的一个总结吧,将学到的知识写下来心里也更有底一点,重温一遍也算是对知识进行一次加深认识吧,虽然做的都是很基础的东西,但是慢慢地去积累吧,要有一颗打磨自己的心。