TCP(Transmission Control Protocol,传输控制协议)是基于连接的协议,也就是说,在正式收发数据前,必须和对方建立可靠的连接,所以也可以说TCP是一种可靠的网络协议,他在通信的两端各建立一个Socket,从而在通信两端形成虚拟的网络链路,
java中对TCP协议的网络通信提供了良好的封装,java使用Socket对象来代表两端的通信端,并通过Socket获取IO流来进行网络通信,在TCP通信协议里,定义了两个网络设备,一个是服务器端,一个是客户端,单独看可以分别对应于UDP的发送端和接收端,但是因为TCP是有连接的协议,下面按照服务器和客户端分别介绍java实现方法;
服务器端:
ServerSocket ss=new ServerSocket(9999); //建立服务器监听端, Socket socket=ss.accept(); //得到一个客户端连接请求并建立socket连接,注意accept()是一个阻塞式方法,
客户端:
Socket s=new Socket("127.0.0.1",9999);//建立socket端,并与指定的IP地址和端口号连接,
建立了socket连接以后剩下的就是IO操作了,在TCP中,首先要通过socket对象来获取输入输出流,
例如:BufferedReader bfr=new BufferedReader(new InputStreamReader(socket.getInputStream()));//将socket输入流封装为字符流,获取这个输入流的目的是为了读取网络中的数据。
PrintWriter pr=new PrintWriter(socket.getOutputStream(),true);//获取socket输出流,用于向网络中特定的连接对象发送数据,这里使用了PrintWriter打印装饰流,
这里特别要提一下,在网络通信的流操作中经常有很多莫名的等待,多是因为阻塞方法造成的,例如在
Tcp
复制文本中,客户端读文件可以自己跳出循环,而服务器端基于
BufferedReader中的readline方法,这个方法是要读到换行符才返回数据,如果发送端没有发送换行符,则接收端会一直等待
读文件则因找不到结束标志,而一直等待,
实现结束标记有多种方法,可以以时间戳来标记,通过DataInputstream实现,也或者通过判断读取的文件长度,是否为0
当然,最合理的方式,是通过socket自带的一个方法, void shutdownOutput() 禁用此套接字的输出流。
该方法用于向接收端写一个结束标志。例如,所以在写发送端的时候,需要在发送完成时加上socket的一个发送结束标志的方法,socket.shutdownOutput();
服务器端代码:
package com.io;
import java.net.*;
import java.io.*;
public class TCPCOPYS {
/**
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
ServerSocket ss=new ServerSocket(9999); //建立一个服务器端
Socket socket=ss.accept(); //获取一个socket连接对象
String ip=socket.getInetAddress().getHostAddress(); //得到连接对象的IP地址
System.out.println(ip+"is connet");
BufferedReader bfr=new BufferedReader(new InputStreamReader(socket.getInputStream()));//获得socket的输入流对象
BufferedWriter bfw=new BufferedWriter(new FileWriter("F:\\IOTest\\a\\source-copy320.txt"));//建立本地的输出流对象
PrintWriter pr=new PrintWriter(socket.getOutputStream(),true);//获得socket的输出流对象
String st=null;
while((st=bfr.readLine())!=null){
//System.out.println("client:"+st);
bfw.write(st); //将获得的输入流数据写到本地输出流的缓冲区中
bfw.newLine();//写一个换行符
bfw.flush();//刷新输出流,将数据写到本地文件中;
//if(st.length()==0)break;
}
System.out.println("复制完了");
pr.println(" 复制成功"); //向客户端发送一个信息
socket.close(); 关闭socket连接
}
}
客户端代码:
package com.io;
import java.net.*;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
public class TCPCOPYC {
public static void main(String []args) throws UnknownHostException, IOException{
Socket s=new Socket("127.0.0.1",9999); //创建一个socket连接,指定IP地址和端口号,
BufferedReader bfr=new BufferedReader(new FileReader("F:\\IOTest\\a\\source.txt"));//创建一个读取流对象
BufferedReader bfrs=new BufferedReader(new InputStreamReader(s.getInputStream()));//创建一个输入流对象,并获得socket的输入流;
// BufferedWriter bfw=new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
PrintWriter pr=new PrintWriter(s.getOutputStream(),true);//创建个输出打印流对象,并获得socket的输出流对象,
String st=null;
while((st=bfr.readLine())!=null){
pr.println(st); //将本地输入流获取的数据发送至服务器端
}
s.shutdownOutput(); //发送完毕后发送一个停止输出流的标志
System.out.println("发送完了");
System.out.println("sever:"+bfrs.readLine()); //显示服务器返回的信息。
s.close();
}
}
例2:通过多线程技术,实现一个服务器端,并且通过多个客户端想服务器端发送图片,服务器将这些图片以客户端的IP地址命名并保存到本地硬盘。
服务器端 :
package com.io;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket
public class TCPCOPYPICS {
/**
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
ServerSocket ss=new ServerSocket(9009); //创建一个服务器端
while(true){
Socket s=ss.accept(); //获取一个socket连接
new Thread(new picTread(s) ).start(); //为socket分配一个线程
}
}
}
class picTread implements Runnable{ //线程类,定义线程方法
public Socket s;
public picTread(Socket s){ //传递socket对象
this.s=s;
}
public void run() {
String ip=s.getInetAddress().getHostAddress();//获取socket连接对象的IP
int i=0; //定义一个文件计数器
try {
System.out.println(ip);
BufferedInputStream bfr=new BufferedInputStream(s.getInputStream());
PrintWriter prouts=new PrintWriter(s.getOutputStream(),true);
File file=new File("F:\\IOTest\\p\\"+ip+"("+i+")"+"p-321.jpg"); //创建一个文件对象
while(file.exists()){file=new File("F:\\IOTest\\p\\"+ip+"("+i+++")"+"p-321.jpg");}//当目录下有file存在是,重新创建一个新的file对象,命名方式通过文件计数器来区分,
BufferedOutputStream bfw=new BufferedOutputStream(new FileOutputStream(file));
byte[]buf=new byte[1024];
int len=0;
while((len=bfr.read(buf))!=-1){
bfw.write(buf, 0, len); //向指定文件中写数据
}
prouts.println("复制成功");
} catch (Exception e) {
throw new RuntimeException(ip+"上传失败");
}
}
}
客户端:
package com.io;
import java.net.*;
import java.io.*;
public class TCPCOPYPICC {
/**
* @param args
* @throws IOException
* @throws UnknownHostException
*/
public static void main(String[] args) throws UnknownHostException, IOException {
// TODO Auto-generated method stub
Socket s=new Socket("127.0.0.1",9009); //创建一个socket连接,
BufferedInputStream bfr=new BufferedInputStream(new FileInputStream("F:\\IOTest\\p\\TI.jpg"));
BufferedOutputStream bfo=new BufferedOutputStream(s.getOutputStream());
BufferedReader bfrs=new BufferedReader(new InputStreamReader(s.getInputStream()));
byte []buf=new byte[1024];
int len=0;
while((len=bfr.read(buf))!=-1){
bfo.write(buf,0,len);
}
System.out.println("发送完了");
s.shutdownOutput(); //向服务器端发送结束标志
System.out.println("sever:"+bfrs.readLine());
}
}
小结:因为TCP通信是基于连接的,在应用的时候,首先应该先建立服务器,让服务器侦听一个指定端口,然后通过客户端去连接服务器,在涉及到外网的时候,我们填写的服务器端IP地址就是服务器的外网IP,在服务器所在局域网内,还需要通过路由器做端口映射;当非正常关闭客户端的时候,服务器端会因为socket异常而退出,另外多线程是网络服务器的基本思路,应该重点掌握。