基于Socket的网络编程
1,什么是Socket
网络上的两个程序通过一个双向的通讯连接实现数据的交换,这个双向链路的一端称为一个Socket。Socket通常用来实现客户方和服务方的连接。Socket是TCP/IP协议的一个十分流行的编程界面,一个Socket由一个IP地址和一个端口号唯一确定。
但是,Socket所支持的协议种类也不光TCP/IP一种,因此两者之间是没有必然联系的。在Java环境下,Socket编程主要是指基于TCP/IP协议的网络编程。
2,Socket通讯的过程
Server端Listen(监听)某个端口是否有连接请求,Client端向Server 端发出Connect(连接)请求,Server端向Client端发回Accept(接受)消息。一个连接就建立起来了。Server端和Client 端都可以通过Send,Write等方法与对方通信。
对于一个功能齐全的Socket,都要包含以下基本结构,其工作过程包含以下四个基本的步骤:
(1) 创建Socket;
(2) 打开连接到Socket的输入/出流;
(3) 按照一定的协议对Socket进行读/写操作;
(4) 关闭Socket.(在实际应用中,并未使用到显示的close,虽然很多文章都推荐如此,不过在我的程序中,可能因为程序本身比较简单,要求不高,所以并未造成什么影响。)
3,创建Socket
创建Socket
java在包java.net中提供了两个类Socket和ServerSocket,分别用来表示双向连接的客户端和服务端。这是两个封装得非常好的类,使用很方便。其构造方法如下:
Socket(InetAddress address, int port);
Socket(InetAddress address, int port, boolean stream);
Socket(String host, int prot);
Socket(String host, int prot, boolean stream);
Socket(SocketImpl impl)
Socket(String host, int port, InetAddress localAddr, int localPort)
Socket(InetAddress address, int port, InetAddress localAddr, int localPort)
ServerSocket(int port);
ServerSocket(int port, int backlog);
ServerSocket(int port, int backlog, InetAddress bindAddr)
其中address、host和port分别是双向连接中另一方的IP地址、主机名和端 口号,stream指明socket是流socket还是数据报socket,localPort表示本地主机的端口号,localAddr和 bindAddr是本地机器的地址(ServerSocket的主机地址),impl是socket的父类,既可以用来创建serverSocket又可 以用来创建Socket。count则表示服务端所能支持的最大连接数。例如:
Socket client = new Socket(“127.0.01.”, 80);
ServerSocket server = new ServerSocket(80);
【注意】在选择端口时,必须小心。每一个端口提供一种特定的服务,只有给出正确的端口,才 能获得相应的服务。0~1023的端口号为系统所保留,例如http服务的端口号为80,telnet服务的端口号为21,ftp服务的端口号为23, 所以我们在选择端口号时,最好选择一个大于1023的数以防止发生冲突。
在创建socket时如果发生错误,将产生IOException,在程序中必须对之作出处理。所以在创建Socket或ServerSocket是必须捕获或抛出异常。
4,简单的Client/Server程序
package test2;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 创建一个服务器端,再创建多线程,使得多个客户端可以同时并发的访问服务器端,表现为当分别打开客户端的时候,我可以随便选一个执行,
* 不会存在必须把先打开的客户端程序执行完毕,才可执行其他客户端的情况
*
* @author Administrator
*
*/
public class MyServer {
public static void main(String[] args) {
try {
ServerSocket server=new ServerSocket(8080);//创建一个服务端,监听一个端口
System.out.println("服务器启动了");
while(true){
Socket socket=server.accept();//获取一个客户端对象
Thread t=new Thread(new Test(socket));
t.start();//启动线程
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
package test2;
/**
* 创建一个客户端,绑定8080端口
*/
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
public class MyClient {
public static void main(String[] args) {
try {
Socket socket=new Socket("127.0.0.1",8080);
//想服务器端发送请求
OutputStream os=socket.getOutputStream();
OutputStreamWriter ss=new OutputStreamWriter(os);
BufferedWriter buf=new BufferedWriter(ss);
System.out.println("客户端1请输入你的请求:");
String str=new Scanner(System.in).nextLine();
buf.write(str);
buf.flush();
socket.shutdownOutput();
//接收服务端
InputStream is=socket.getInputStream();
InputStreamReader in=new InputStreamReader(is);
BufferedReader sy=new BufferedReader(in);
String st="接收到的服务端回应:";
StringBuffer tt=new StringBuffer();
while((str=sy.readLine())!=null){
tt.append(str);
}
System.out.println(tt);
socket.shutdownInput();
} catch (Exception e) {
e.printStackTrace();
}
}
}
package test2;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.util.Scanner;
/**
* 创建另一个客户端,也绑定8080端口
* @author Administrator
*
*/
public class MyClient2 {
public static void main(String[]args){
try {
Socket socket=new Socket("127.0.0.1",8080);
//想服务器端发送请求
OutputStream os=socket.getOutputStream();
OutputStreamWriter ss=new OutputStreamWriter(os);
BufferedWriter buf=new BufferedWriter(ss);
System.out.println("客户端2请输入你的请求:");
String str=new Scanner(System.in).nextLine();
buf.write(str);
buf.flush();
socket.shutdownOutput();
//接收服务端
InputStream is=socket.getInputStream();
InputStreamReader in=new InputStreamReader(is);
BufferedReader sy=new BufferedReader(in);
String st="接收到的服务端回应:";
StringBuffer tt=new StringBuffer();
while((str=sy.readLine())!=null){
tt.append(str);
}
System.out.println(tt);
socket.shutdownInput();
} catch (Exception e) {
e.printStackTrace();
}
}
}
package test2;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
public class Test implements Runnable{
private Socket socket;
public Test(Socket socket){
this.socket=socket;
}
public void run(){
try {
//只有获取到客户端对象,才会执行
System.out.println("获取到一个连接");
//读取客户端发送的数据
InputStream is=socket.getInputStream();
InputStreamReader in=new InputStreamReader(is);
BufferedReader ss=new BufferedReader(in);
System.out.println("接收到的客户端请求:");
String str="";
StringBuffer tt=new StringBuffer();
while((str=ss.readLine())!=null){
tt.append(str);
}
System.out.println(tt);
socket.shutdownInput();
//回应客户端
OutputStream os=socket.getOutputStream();
PrintWriter pw=new PrintWriter(os);//打印出来
pw.print("我已接收到了!");
pw.flush();//刷新打印
socket.shutdownOutput();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
【注意】首先打开服务器端,再打开客户端程序
(server端)运行结果:
服务器启动了
(客户端1)运行结果:
客户端1请输入你的请求:
(server端)运行结果:
服务器启动了
获取到一个连接
接收到的客户端请求:
(客户端2)运行结果:
客户端2请输入你的请求:
happy year
我已接收到了!
(server端)运行结果:
服务器启动了
获取到一个连接
接收到的客户端请求:
获取到一个连接
接收到的客户端请求:
happy year
(客户端1)运行结果:
客户端1请输入你的请求:
tom
我已接收到了!
(server端)运行结果:
服务器启动了
获取到一个连接
接收到的客户端请求:
获取到一个连接
接收到的客户端请求:
happy year
tom
5,支持多客户的client/server程序
之前的Client/Server程序只能实现Server和一个客户的对话。在实际应用 中,往往是在服务器上运行一个永久的程序,它可以接收来自其他多个客户端的请求,提供相应的服务。为了实现在服务器方给多个客户提供服务的功能,于是对上 面的程序进行了改造,利用多线程实现多客户机制。服务器总是在指定的端口上监听是否有客户请求,一旦监听到客户请求,服务器就会启动一个专门的服务线程来响 应该客户的请求,而服务器本身在启动完线程之后马上又进入监听状态,等待下一个客户的到来。
反射机制
1、反射机制是什么
反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
反射机制主要提供了以下功能:
(1)在运行时判断任意一个对象所属的类;
(2)在运行时构造任意一个类的对象;
(3)在运行时判断任意一个类所具有的成员变量和方法;
(4)在运行时调用任意一个对象的方法;
(5)生成动态代理。
2、反射机制的相关API
获取方法,和构造方法,来看一下关键字:
package test3;
public class User {
public String name="张三";
private int age=24;
private int score;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
public void study(){
System.out.println("在伤心");
}
public void study(String name,int age){
System.out.println(age+"岁的"+name+"在上学");
}
}
package test3;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
public class Refelct {
public static void main(String[] args) {
//通过一个对象获得完整的类名和包名
User user=new User();
Class c=user.getClass();
System.out.println(c.getClass().getName()+","+c.getName());//test3.User
//另一种写法
/*try {
Class q = Class.forName("test3.User");
User users=new User();
users.study();
System.out.println(q.getName());
} catch (ClassNotFoundException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}*/
System.out.println("*******************************");
//获取类里面所有声明的属性(成员变量)
Field fields[]=c.getDeclaredFields();
for(Field f:fields){
System.out.println(Modifier.toString(f.getModifiers())+" "+f.getType()+" "+f.getName());
}
System.out.println("*************************");
//获取类里面所有声明的方法
Method methods[]=c.getDeclaredMethods();
for(Method m:methods){
System.out.println(Modifier.toString(m.getModifiers())+" "+m.getReturnType()+" "+m.getName());
}
//获得成员方法的参数类型
Class cs[]=methods[4].getParameterTypes();
for(Class t:cs){
System.out.println(t.getName());
}
System.out.println("+++++++++++++++++++++++++++++++++++++");
try {
// 调用User类中的study()方法
Method method=c.getDeclaredMethod("study");
method.invoke(user);
// 调用User类中的study(String name,int age)方法
Method method2=c.getDeclaredMethod("study", String.class,int.class);
method2.invoke(user,"jack",20);//user对象中执行有参数String和int类型的method方法
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
//通过反射机制修改成员属性
System.out.println("要求更改属性值:");
System.out.println("修改前的年龄是:"+user.getAge());
fields[1].setAccessible(true);//单个修改访问权限
AccessibleObject.setAccessible(fields, true);//修改整个数组的权限
fields[1].set(user,100);
System.out.println("修改后的年龄是:"+user.getAge());
System.out.println("修改前的姓名是:"+user.name);
fields[0].setAccessible(true);
fields[0].set(user,"李四");
System.out.println("修改后的姓名是:"+user.name);
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果:
java.lang.Class,test3.User
*******************************
public class java.lang.String name
private int age
private int score
*************************
public void study
public void study
public int getAge
public void setAge
public int getScore
public void setScore
+++++++++++++++++++++++++++++++++++++
在伤心
20岁的jack在上学
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
要求更改属性值:
修改前的年龄是:24
修改后的年龄是:100
修改前的姓名是:张三
修改后的姓名是:李四
3、反射加配置文件,使我们的程序更加灵活
学习抽象工厂的时候就用到了反射来更加方便的读取数据库链接字符串,还有JAVA中的配置文件为.properties,称作属性文件,通过反射读取里边的内容,这样代码虽然是固定的,但是配置文件的内容我们可以改,这样使我们的代码灵活了很多!但是它也有它的缺点,就是运用它会使我们的软件的性能降低,复杂度增加,所以还要我们慎重的使用它。