1 TCP传输
TCP特点:
1,建立连接,形成传输数据的通道。
2,在连接中进行大数据量传输。
3,通过三次握手完成连接,是可靠协议。
4,必须建立连接,效率会稍低。
演示tcp传输,客户端向服务端发送数据。
1,tcp分客户端和服务端。
2,客户端对应的对象是Socket。
服务端对应的对象是ServerSocket。
3,建立连接后,通过Socket中的IO流进行数据的传输。
4,关闭socket。
同样,客户端与服务器端是两个独立的应用程序。
客户端:
通过查阅Socket对象,发现在该对象建立时,就可以去连接指定主机。
因为tcp是面向连接的,所以在建立Socket服务时,就要有服务端存在,并连接成功。
形成通路后,在该通道进行数据传输。通路一建立,Socket流就存在了,包括输入流和输出流。
需求:给服务端发送一个文本数据。
步骤:
1,创建Socket服务,并指定要连接的主机和端口。
2,调用Socket的方法,获取输入流或输出流。
服务端:
需求:定义端点,接收数据,并打印在控制台上。
服务端没有流对象,拿客户端的流对象和客户端通信。
步骤:
1,建立服务端的Socket服务。ServerSocket();
并监听一个端口。(其实就是给该应用程序打数字标识)
2,获取连接过来的客户端对象。
通过ServerSocket的 accept方法。没有连接就会等,所以这个方法是阻塞式的。
3,客户端如果发过来数据,那么服务端要使用对应的客户端对象,并获取到该客户端对象的读取流来读取发过来的数据,并打印在控制台。
4,关闭服务端(可选操作)。
代码及注释:
import java.net.*;
import java.io.*;
class TcpClient{
public static void main(String[] args) throws Exception{
//创建客户端的Socket服务,指定目的主机和端口。
Socket s = new Socket("127.0.0.1",10003); //必须指定端口建立连接
//为了发送数据,应该获取socket流中的输出流。
OutputStream out = s.getOutputStream();
//把数据写入socket通道中,服务端再从socket通道读取数据。
out.write("tcp 哥们来了".getBytes());
s.close();
}
}
class TcpServer {
public static void main(String[] args) throws IOException{
//建立服务端的socket服务,并监听一个端口。
ServerSocket ss = new ServerSocket(10003);
//通过accept方法获取连接过来的客户端对象。
Socket s = ss.accept(); //阻塞式方法
String ip = s.getInetAddress().getHostAddress();
System.out.println(ip+"......connected");
//获取客户端发送过来的数据,那么要使用客户端对象的读取流来读取数据。
InputStream in = s.getInputStream();
byte[] buf = new byte[1024];
int len = in.read(buf);
System.out.println(new String(buf,0,len));
s.close(); //关闭客户端。
ss.close(); //关闭服务端,可选操作。
}
}
2 TCP传输第二个例子
演示tcp的传输的客户端和服务端的互访,上一个例子中只是客户端向服务端发送数据。
需求:客户端给服务端发送数据,服务端收到后,给客户端反馈信息。
客户端:
1,建立Socket服务,指定要连接的主机和端口。
2,获取socket流中的输出流,将数据写到该流中,通过网络发送给服务端。
3,获取socket流中的输入流,将服务端反馈的数据获取到,并打印。
4,关闭客户端资源。
服务端:
1,建立ServerSocket服务,指定监听的端口。
2,获取连接过来的客户端的Socket对象。
3,获取Socket对象的输入流,用以接收客户端传送过来的数据。
4,获取Socket对象的输出流,用以服务端向客户端发送反馈数据。
5,关闭客户端资源,关闭服务端资源(可选操作)。
代码和注释:
import java.net.*;
import java.io.*;
class TcpClient2 {
public static void main(String[] args) throws Exception{
Socket s = new Socket("192.168.0.100",10004);//建立客户端socket服务,指定目的主机和端口。
OutputStream out = s.getOutputStream(); //输出流,用以向服务端发送数据。
out.write("服务端,你好".getBytes()); //数据写入socket通道中。
InputStream in = s.getInputStream(); //输入流,用以读取服务端返回的数据。
byte[] buf = new byte[1024];
int len = in.read(buf);
System.out.println(new String(buf,0,len));
s.close(); //关闭服务端的socket
}
}
class TcpServer2 {
public static void main(String[] args) throws Exception{
ServerSocket ss = new ServerSocket(10004);//建立服务端socket服务,指定监听的端口。
Socket s = ss.accept(); //获取连接过来的客户端socket对象。
String ip = s.getInetAddress().getHostAddress(); //获取客户端IP地址。
System.out.println(ip+"......connected");
InputStream in = s.getInputStream(); //Socket输入流,用以接收客户端传送过来的数据。
byte[] buf = new byte[1024];
int len = in.read(buf);
System.out.println(new String(buf,0,len));
OutputStream out = s.getOutputStream();//输出流,用以发送服务端给客户端的反馈数据。
out.write("我收到,客户端,你好".getBytes()); //把数据写入socket通道中.
s.close();
ss.close();
}
}
3 TCP传输练习:文本转换
需求:建立一个文本转换服务器。
客户端给服务端发送文本,服务端会将文本转成大写再返回给客户端。
而且客户端可以不断的进行文本转换。当客户端输入over时,转换结束。
客户端:
既然是操作设备上的数据,就可以使用IO技术,并按照IO操作的规律来思考。
源:键盘录入。
目的:Socket输出流。
步骤:
1,建立服务。
2,获取键盘录入。
3,将数据发给服务端。
4,获取服务端返回的大写数据。
5,结束,关资源。
都是文本数据,可以使用字符流进行操作,同时提高效率,加入缓冲。
服务端:
源:socket读取流。
目的:socket输出流。
都是文本,装饰。
该例子会出现的问题。
现象:客户端和服务端都在莫名的等待。为什么呢?
因为客户端和服务端都有阻塞式方法。这些方法没有读到结束标记,那么就一直等待。而导致两端,都在等待。解决方法见下一节。
关键词:阻塞式方法。
代码示例:
import java.net.*;
import java.io.*;
class TransClient {
public static void main(String[] args) throws Exception{
Socket s = new Socket("192.168.0.100",10005); //建立Socket并连接到目的主机
//定义读取键盘数据的流对象。
BufferedReader bufr =
new BufferedReader(new InputStreamReader(System.in));//源是键盘录入。
//定义目的,将数据写入到socket输出流,发给服务端。
BufferedWriter bufOut =
new BufferedWriter(new OutputStreamWriter(s.getOutputStream())); //目的是socket输出流。
//定义一个socket读取流,读取服务端返回的大写信息。
BufferedReader bufIn =
new BufferedReader(new InputStreamReader(s.getInputStream()));
String line = null;
while((line=bufr.readLine())!=null){
if(line.equals("over"))
break;
bufOut.write(line);
bufOut.newLine();
bufOut.flush();
String str = bufIn.readLine();
System.out.println("server:"+str);
}
bufr.close();
s.close();
}
}
class TransServer {
public static void main(String[] args) throws Exception{
ServerSocket ss = new ServerSocket(10005);//监听10005端口
Socket s = ss.accept(); //获取客户端的Socket对象,ServerSocket没有IO流
String ip = s.getInetAddress().getHostAddress();
System.out.println(ip+"......connected");
//读取socket读取流中的数据。源。
BufferedReader bufIn =
new BufferedReader(new InputStreamReader(s.getInputStream()));
//目的:socket输出流。将大写数据写入到socket输出流,发送给客户端。
BufferedWriter bufOut =
new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
//打印流的println自动刷新缓冲区,而且打印流可接收字节流和字符流。
// PrintWriter bufOut = new PrintWriter(s.getOutputStream, true);
String line = null;
while((line=bufIn.readLine())!=null){
System.out.println(line);
bufOut.write(line.toUpperCase());//toUpperCase()字符串转大写。
bufOut.newLine();
bufOut.flush();
}
s.close();
ss.close();
}
}
4 TCP客户端并发上传图片
客户端:
1,创建Socket服务并连接到服务端。
2,读取客户端本地已有的图片数据。
3,通过socket输出流,将数据发给服务端。
4,通过socket读取流,读取服务端反馈信息。
5,关闭资源。
服务端:
1,创建ServerSocket服务并监听端口。
2,获取客户端的Socket对象。
3,通过Socket对象的socket读取流,读取客户端发来的数据。
4,通过Socket对象的socket输出流,把反馈信息发送给客户端。
5,关闭资源。
这个服务端有个局限性,当A客户端连接上以后,被服务端获取到,服务端执行具体流程。
这时B客户端连接服务端,就只能等待。
因为服务端还没有处理完A客户端的请求,还没有循环回来执行下次accept方法。
所以暂时获取不到B客户端对象。
为了可以让多个客户端同时并发访问服务端,即服务端可以同时处理多个客户端的请求。
那么服务端就用到多线程,就把对每个客户端请求的处理,封装到一个单独的线程中,这样,就可以同时处理多个客户端请求。
如何定义线程呢?
只要明确了每一个客户端要在服务端执行的代码即可,将该代码存入run方法中。
代码示例:
import java.net.*;
import java.io.*;
class PicClient {
public static void main(String[] args) throws Exception{
//只能传一个参数,即只能指定一个路径,一次只能上传一个文件。
if(args.length!=1){
System.out.println("请选择一个jpg格式的图片");
return;
}
File file = new File(args[0]);
if(!(file.exists() && file.isFile())){ //文件不存在或不是文件
System.out.println("该文件有问题,要么不存在,要么不是文件");
return;
}
if(!(file.getName().endsWith(".jpg"))){ //不是jpg图片文件
System.out.println("图片格式错误,请重新选择");
return;
}
if(file.length()>1024*1024*5){ //图片不能大于5M
System.out.println("图片过大");
return;
}
Socket s = new Socket("192.168.0.100",10007);//创建Socket并连接到服务端
//要上传的文件,命令:“java PicClient e:\EVE.jpg”
FileInputStream fis = new FileInputStream(file);
OutputStream out = s.getOutputStream();//Socket输出流
byte[] buf = new byte[1024];
int len = 0;
while((len=fis.read(buf))!=-1){
out.write(buf,0,len);
}
// 告诉服务端,数据已写完,送一个结束标记过去,阻塞方法继续执行。
s.shutdownOutput(); //禁用此Socket的输出流,任何以前写的数据都将发送。
InputStream in = s.getInputStream(); //读取服务端的反馈信息。
byte[] bufIn = new byte[1024];
int num = in.read(bufIn);
System.out.println(new String(bufIn,0,num));
fis.close();
s.close();
}
}
class PicServer {
public static void main(String[] args) throws Exception{
ServerSocket ss = new ServerSocket(10007);//监听指定端口
while(true){
Socket s = ss.accept(); //获取客户端的Socket对象。
new Thread(new PicThread(s)).start(); //对客户端请求的处理封装在线程中。
}
}
}
class PicThread implements Runnable {
private Socket s;
PicThread(Socket s){
this.s = s;
}
public void run(){
int count = 1;
String ip = s.getInetAddress().getHostAddress();
try{
System.out.println(ip+".....connected");
InputStream in = s.getInputStream(); //获取Socket输入流
//实现多次上传同一个文件,不覆盖。
File file = new File("e:\\"+ip+"("+count+")"+".jpg");
while(file.exists())
file = new File("e:\\"+ip+"("+(count++)+")"+".jpg");
FileOutputStream fos = new FileOutputStream(file); //输出到文件
byte[] buf = new byte[1024];
int len = 0;
while((len=in.read(buf))!=-1){
fos.write(buf,0,len);
}
OutputStream out = s.getOutputStream(); //向客户端发送反馈信息。
out.write("server:上传图片成功".getBytes());
fos.close();
s.close();
}
catch(Exception e){
throw new RuntimeException(ip+"上传失败");
}
}
}
5 TCP客户端并发登录
客户端通过键盘录入用户名。服务端对这个用户名进行校验。
如果该用户存在,在服务端显示“xxx,已登录”。并在客户端显示“xxx,欢迎光临”。
如果该用户不存在,在服务端显示“xxx,尝试登录”。并在客户端显示“xxx,该用户不存在”。
最多就登录三次。
用户名写在user.txt中,保存到当前ClassPath目录。
代码示例:
import java.net.*;
import java.io.*;
class LoginClient{
public static void main(String[] args) throws Exception{
Socket s = new Socket("192.168.0.100",10008); //创建Socket并连接到服务端。
BufferedReader bufr =
new BufferedReader(new InputStreamReader(System.in));//键盘录入
PrintWriter out =
new PrintWriter(s.getOutputStream(),true);//true代表println自动刷新缓冲区。
BufferedReader bufIn =
new BufferedReader(new InputStreamReader(s.getInputStream()));
for(int x=0; x<3; x++){ //最多登录三次
String line = bufr.readLine();
if(line==null)
break;
out.println(line);
String info = bufIn.readLine();
System.out.println("info: "+info);
if(info.contains("欢迎"))
break;
}
bufr.close();
s.close();
}
}
class LoginServer {
public static void main(String[] args) throws Exception{
ServerSocket ss = new ServerSocket(10008);//创建服务端的ServerSocket
while(true){
Socket s = ss.accept(); //获取客户端的Socket对象。
//服务端对客户端请求的处理,封装在线程的run方法中。
new Thread(new UserThread(s)).start();
}
}
}
class UserThread implements Runnable{
private Socket s;
UserThread(Socket s){ //客户端的Socket对象
this.s = s;
}
public void run(){
String ip = s.getInetAddress().getHostAddress();
System.out.println(ip+".....connected");
try{
for(int x=0; x<3; x++){ //最多登录三次
BufferedReader bufIn = //获取Socket输入流,读取客户端发送来的数据。
new BufferedReader(new InputStreamReader(s.getInputStream()));
String name = bufIn.readLine();
BufferedReader bufr = new BufferedReader(new FileReader("user.txt"));
PrintWriter out = new PrintWriter(s.getOutputStream(),true); //Socket输出流
String line = null;
boolean flag = false;
while((line=bufr.readLine())!=null){
if(line.equals(name)){ //true,则登录成功,否则失败。
flag = true;
break;
}
}
if(flag){
System.out.println(name+",已登录");
out.println(name+",欢迎光临");
break;
}
else{
System.out.println(name+",尝试登录");
out.println(name+",用户名不存在");
}
}
s.close();
}
catch(Exception e){
throw new RuntimeException(ip+"登录失败");
}
}
}