TCP协议要比UDP协议复杂一些,UDP在传输数据时不会检查是否连接,而TCP在传输前需要先建立连接(通过服务端和客户端三次握手)。
因此在在测试的时候也一定要先开启TCP服务端,然后在开启客户端,TCP简单示例如下
package net;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
/*
* tcp传输
*
* 1.tcp分客户端和服务端
* 2.客户端对应的对象是socket
* 服务端对应的对象时ServerSocket
*/
public class TCPDemo {
public static void main(String[] args) {
new TCPClient();
new TCPServer();
}
}
/*
* 客户端
* 通过查阅socket对象,发现在该对象建立时,就可以去连接指定主机
* 因为有服务端存在,并连接成功,形成通路后,在钙通道进行数据传输
*
* 1. 创建socket服务并指定要连接的主机和端口
* 2. 向服务端发送数据
* 3. 关闭资源
*/
class TCPClient{
public static void main(String[] args) {
try {
//创建客户端的socket服务,指定目标主机和端口
Socket s = new Socket("10.2.20.82",9596);
//为了发送数据,应该获取socket流中的输出流
OutputStream out = s.getOutputStream();
out.write("TCP 客户端发送数据".getBytes());
//客户端接收信息
InputStream in = s.getInputStream();
byte[] buf = new byte[1024];
int len = in.read(buf);
String data = new String(buf,0,len);
System.out.println(data);
s.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/*
* 服务端
*
* 1. 使用ServerSocket,建立服务端socket服务,并监听一个端口
* 2. 通过ServerSocket的accept()方法,获取连接的客户端对象,这个方法是阻塞式的
* 3. 客户端如果发过来数据,那么服务端要使用对应的客户端对象,并获取到该客户端对象的读取流来读取发过来的数据
* 4. 关闭资源(关闭客户端)
*/
class TCPServer{
public static void main(String[] args) {
try {
//建立服务端socket,并监听端口
ServerSocket ss = new ServerSocket(9596);
//通过accept()方法获取链接过来的客户端对象
Socket s = ss.accept();
String ip = s.getInetAddress().getHostAddress();
//获取客户端发送过来的数据,那么要使用客户端对象的读取流来读取数据
InputStream in = s.getInputStream();
byte[] buf = new byte[1024];
int len = in.read(buf);
String data = new String(buf,0,len);
System.out.println(ip+"..."+data);
//服务端返回消息
OutputStream out = s.getOutputStream();
out.write("收到".getBytes());
s.close();
ss.close();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println();
}
}
练习:从客户端上传一个非文本文件到服务端的硬盘上保存
package net;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
/*
* 练习:上传文件,文件非文本,最终保存在磁盘上
*
* 1.与服务端建立连接
* 2.通过字节流读取文件,然后写到socket的输出流中向服务端发送
* 注意:文件传输完后应该有个结束标记,否则因为客户端和服务端都在使用read()方法,造成都在等待而不能结束程序,
* 这里使用socket的shutdownOutput()方法,这个方法就是向socket输出流结尾写一个"-1"
* 3.服务端接收socket输入流中的数据,然后使用字节流写到指定的磁盘位置,写完后向客户端向socket输出流写入返回语句,表示"上传成功",
* 4.客户端接收服务端的消息并打印
* 5.服务器端关闭资源,客户端关闭资源
*/
public class UploadDemo {
public static void main(String[] args) {
new Client();
new Server();
}
}
class Client{
public static void main(String[] args) {
try {
Socket socket = new Socket("10.2.20.82",9696);
//从磁盘读取文件
File file = new File("E:\\BaiduYunDownload\\FILE\\1.mp3");
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
int len = 0;
byte[] buf = new byte[1024];
//将从文件读取出的数据发送给服务端
BufferedOutputStream sbos = new BufferedOutputStream(socket.getOutputStream());
while((len=bis.read(buf))!=-1){
sbos.write(buf,0,len);
sbos.flush();
}
//用于关闭客户端的socket输出流
socket.shutdownOutput();
//接收服务端返回信息,并打印
BufferedInputStream sbis = new BufferedInputStream(socket.getInputStream());
while((len=sbis.read(buf))!=-1){
System.out.println(new String(buf,0,len));
}
//关闭资源
bis.close();
socket.close();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
class Server{
public static void main(String[] args) {
try {
//服务端监听端口
ServerSocket serverSocket = new ServerSocket(9696);
Socket socket = serverSocket.accept();
//设置文件输出位置
File file = new File("E:\\BaiduYunDownload\\FILE\\2.mp3");
long time = System.currentTimeMillis();
//读取数据
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file));
BufferedInputStream sbis = new BufferedInputStream(socket.getInputStream());
int len = 0;
byte[] buf = new byte[1024];
//将读取的数据输出到指定文件中
System.out.println("接收文件开始");
while((len=sbis.read(buf))!=-1){
bos.write(buf, 0, len);
bos.flush();
}
System.out.println("接收文件结束");
System.out.println("用时"+(System.currentTimeMillis()-time)+"ms");
//向客户端反馈信息
BufferedOutputStream sbos = new BufferedOutputStream(socket.getOutputStream());
sbos.write("上传成功".getBytes());
sbos.flush();
//关闭资源
bos.close();
socket.close();
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
练习2:并发的上传图片,注意校验文件的有效性,图片格式为.jpg
package net;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
/*
* 练习:并发上传图片,格式为.jpg
*/
public class UploadPicDemo {
public static void main(String[] args) {
new PicClient();
new PicServer();
}
}
/*
* 客户端
* 1.连接服务端
* 2.读取客户端已有的图片数据
* 3.通过socket输出流将数据发送给服务端
* 4.读取服务端反馈信息
* 5.关闭资源
*/
class PicClient{
public static void main(String[] args) {
//判断输入的是否为路径
if(args.length!=1){
System.out.println("路径不正确");
return;
}
//判断输入的文件是不是一个有效文件
File file = new File(args[0]);
if(!(file.exists()&&!file.isHidden()&&file.isFile())){
System.out.println("请选择一个图片文件");
return;
}
//判断文件是不是.jpg
if(!file.getName().endsWith(".jpg")){
System.out.println("图片格式不正确");
return;
}
//判断文件是否过大
if(file.length()>1024*1024*5){
System.out.println("图片过大");
return;
}
try {
//客户端创建socket
Socket s = new Socket("10.2.20.82",9697);
OutputStream out = s.getOutputStream();
//读取文件,然后加到socket的输出流中
FileInputStream fis = new FileInputStream(file);
int len = 0;
byte[] buf = new byte[1024];
while((len=fis.read(buf))!=-1){
out.write(buf, 0, len);
}
//关闭socket的输出流
s.shutdownOutput();
//从socket中读取服务端反馈信息
InputStream in = s.getInputStream();
len=in.read(buf);
System.out.println(new String(buf,0,len));
//关闭资源
fis.close();
s.close();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/*
* 服务端
*
* 问题:这个服务端有一个局限性,当多个客户端想要上传文件时,只要有一个客户端在上传文件,那么其他客户端只能等待。
* 解决方式:为了解决这样的等待问题,应采取并发的上传方式,也就是将每个客户端都封装到一个线程中,这样就可以处理多个客户端同时上传的请求
*
* 1.明确客户端要执行的代码,将该代码封装到run方法中
*/
class PicServer{
public static void main(String[] args) {
try {
//服务端监听端口
ServerSocket ss = new ServerSocket(9697);
while(true){
//使用多线程并发接收客户端连接
Socket s = ss.accept();
new Thread(new PicThread(s)).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
//服务端线程中的内容
class PicThread implements Runnable{
private Socket s;
public PicThread(Socket s){
this.s=s;
}
public void run() {
try {
//获取连接的客户端IP
String ip = s.getInetAddress().getHostAddress();
//用于重复上传时在文件名称添加的字段
int count = 1;
File file = new File("E:\\BaiduYunDownload\\FILE\\"+ip+".jpg");
//判断文件是否重复
while(file.exists()){
file = new File("E:\\BaiduYunDownload\\FILE\\"+ip+"("+(count++)+").jpg");
}
FileOutputStream fos = new FileOutputStream(file);
InputStream in = s.getInputStream();
int len = 0;
byte[] buf = new byte[1024];
//将socket传过来的文件,输出到服务端指定位置
while((len=in.read(buf))!=-1){
fos.write(buf, 0, len);
}
//向客户端反馈信息
OutputStream out = s.getOutputStream();
out.write((ip+"上传成功").getBytes());
fos.close();
s.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
下面简单了解一下TCP三次握手都发了什么内容:
package net;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
/**
* 演示客户端和服务端
*
* 1.
* 客户端:浏览器
* 服务端:自定义
*
*/
public class IEServerDemo {
public static void main(String[] args) {
try {
ServerSocket ss = new ServerSocket(9797);
int count=1;
//使用循环接收浏览器的消息
while(true){
Socket s = ss.accept();
String ip = s.getInetAddress().getHostAddress();
System.out.println(ip+"..."+count++);
//接收浏览器(客户端)反馈的信息
InputStream in= s.getInputStream();
int len = 0;
byte[] buf = new byte[1024];
len=in.read(buf);
System.out.println(new String(buf,0,len));
//向浏览器发送数据
PrintWriter pw = new PrintWriter(s.getOutputStream(),true);
pw.println("nihao");
s.close();
}
// ss.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/*
*
TCP三次握手
第一次 客户端向服务器发送
GET / HTTP/1.1
Accept: text/html, application/xhtml+xml,
DNT: 1
Accept-Language: zh-CN
User-Agent: Mozilla/5.0 (Windows NT 6.1; Trident/7.0; rv:11.0) like Gecko LBBROWSER
Accept-Encoding: gzip, deflate
Host: 10.2.20.82:9797
Connection: Keep-Alive
第二次 服务器向客户端反馈
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Accept-Ranges: bytes
ETag: W/"50-1402473194507"
Last-Modified: Wed, 11 Jun 2014 07:53:14 GMT
Content-Type: text/html
Content-Length: 50
Date: Wed, 11 Jun 2014 07:57:27 GMT
第三次 客户端向服务器反馈
GET /favicon.ico HTTP/1.1
Connection: Keep-Alive
Accept:
Accept-Language: zh-CN
User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; LBBROWSER)
Host: 10.2.20.82:9797
*/
/**
* 演示客户端和服务端
*
* 2.
* 客户端:自定义
* 服务端:Tomcat
*/
class IEClient{
public static void main(String[] args) {
try {
Socket s = new Socket("127.0.0.1",8080);
//配置向服务器发送的信息
PrintWriter out = new PrintWriter(s.getOutputStream(),true);
//请求方式
out.println("GET /test/test1.html HTTP/1.1");
//接收类型
out.println("Accept: */*");
//识别语言
out.println("Accept-Language: zh-CN");
//主机地址
out.println("Host: 10.2.20.82:9797");
//保持联通
out.println("Connection: Keep-Alive");
//这里需要空一行,用于把请求头和内容分开
out.println();
//接收服务器反馈信息
InputStream in = s.getInputStream();
int len=0;
byte[] buf = new byte[1024];
len=in.read(buf);
//把内容打印出来
System.out.println(new String(buf,0,len));
s.close();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
最后有个小补充,就是URL这个类
package net;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
public class URLDemo {
public static void main(String[] args) {
try {
URL url = new URL("http://10.2.20.82:8080/test/1.html?username=AAA&password=BBB");
//URL中封装好了的一些方法,用于获取信息
System.out.println("protocol:"+url.getProtocol());//获取协议
System.out.println("host:"+url.getHost());//获取主机地址
System.out.println("port:"+url.getPort());//获取端口号
System.out.println("path:"+url.getPath());//获取资源路径
System.out.println("file:"+url.getFile());//获取资源路径+参数
System.out.println("query:"+url.getQuery());//获取参数
//通过URL连接
URLConnection conn = new URL("http://www.baidu.com.cn").openConnection();
//获取连接流,URLConnection封装了socket的获取流的方法,所以使用方法是一样的
InputStream in = conn.getInputStream();
byte[] buf = new byte[1024];
int len = 0;
//获取服务器的返回数据
while((len=in.read(buf))!=-1){
System.out.println(new String(buf,0,len));
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}