TCP通信
server:
package day07;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 服务端应用程序
* @author Administrator
*
*/
public class Server {
//运行在服务端的Socket
private ServerSocket server;
/**
* 构造方法,用于初始化服务端
* @throws IOException
*/
public Server() throws IOException{
try {
/*
* 创建ServerSocket时需要指定服务端端口
*/
System.out.println("初始化服务端");
server = new ServerSocket(8088);
System.out.println("服务端初始化完毕");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
//没有创建成功。抛出异常
throw e;
}
}
/**
* 服务端开始工作的方法
*/
public void start(){
try{
/*
* ServerSocket的accept方法
* 用于监听8088端口,等待客户端的链接
* 该方法是一个阻塞方法,直到一个客户端链接,否则该方法一直阻塞。
* 若一个客户端链接了,会返回该客户端的Socket
*
*/
System.out.println("等待客户端连接......");
Socket socket=server.accept();
System.out.println("客户端连接了");
//接收来自客户端的消息
//InputStream输入流
InputStream is = socket.getInputStream();
//变成字符流!!!
InputStreamReader isr = new InputStreamReader(is);
//将字符流转换为缓冲流输入流,这样就可以按行为单位读取字符串了
BufferedReader br = new BufferedReader(isr);
String message;
while((message=br.readLine())!=null){
System.out.println("客户端:"+"IP:"+socket.getInetAddress()+":"+socket.getPort()+"::"+message);
}
//若readLine返回null就表示无法再读取到信息
//windows:当客户端与服务端断开连接后readLine()方法会抛出异常
}catch(Exception e){
e.printStackTrace();
}
}
public static void main(String[] args){
Server server = null;
try {
server = new Server();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println("服务端初始化失败!");
}
server.start();
}
}
client:
package day07;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;
public class Client {
//Socket,用于连接服务端的ServerSocket
private Socket socket;
//构造方法,初始化client
public Client() {
try {
/*
* 创建Socket对象时,就会尝试根据给定的地址与端口连接服务器。所以,
* 若该对象创建成功,说明与服务端连接正常
*/
socket = new Socket("127.0.0.1",8088);
System.out.println("成功连接服务端");
} catch (UnknownHostException e) {
//端口号异常
e.printStackTrace();
} catch (IOException e) {
System.out.println("客户端创建失败");
e.printStackTrace();
}
}
//客户端启动方法
public void start(){
try{
/*
* 可以通过Socket的getOutputStream()
* 方法获取一条输出流,用户与信息发送至服务端
* 是一个字节流,
*/
OutputStream out = socket.getOutputStream();
//转换成字符流,写更容易些。
OutputStreamWriter osw = new OutputStreamWriter(out);
//在包装上缓冲流 以行为单位写字符串
PrintWriter pw = new PrintWriter(osw);
while(true){
Scanner scanner = new Scanner(System.in);
pw.println(scanner.next());
//不写的话就不会显示,因为是缓冲流。并没有输出。自己flush一下。
pw.flush();
}
}catch(Exception e){
e.printStackTrace();
}
}
public static void main(String[] args) {
Client client = new Client();
client.start();
}
}
小结:
创建一个类,然后定义个socket,作为server和client,开始只是定义了是Socket类型,并没有初始化。实现构造函数,server的构造函数写的是端口,server = new ServerSocket();ip就是运行在哪个上面就是哪个了。client是传入的服务器的ip和端口 socket = new Socket(ip,端口)
然后都要创建个start函数用作sockt连接后的功能。
server要等待连接; accept 调用accpet是在start中的。
Socket socket=server.accept();
创建的socket是 连接客户端的 socket
然后在主函数中创建个 server
server = new Server();调用 start函数
然后是在start中写如何传输字符。socket有个getOutputStream方法,但是返回的是字节流,那么就要转成字符流 就是 OutputStreamWriter ,然后在加个缓冲流,一行一行的读写操作 PrintWriter
server: getInputStream InputStreamReader BufferedReader
clent: getOutputStream outputStreamWriter
PrintWriter
读取操作,若readLine返回的是null那么就是没有信息在读了。
要注意 flush()
多线程服务端
package day07;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
/**
* java多线程socket
* @author Administrator
*
*/
public class ThreadServer2 {
private ServerSocket server;
public ThreadServer2(){
try {
server = new ServerSocket(8088);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void start(){
while(true){
System.out.println("服务器等待连接.....");
try {
Socket socket = server.accept();
//调用线程,传入socket
hthread ht1 = new hthread(socket);
ht1.start();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}//catch
}//while
}//start
public static void main(String[] args) {
//直接创建启动服务器
new ThreadServer2().start();
}
//线程类,在start中调用线程,并传入socket
class hthread extends Thread {
private Socket socket;
public hthread (Socket socket){
this.socket = socket;
System.out.println("客户端连接成功!");
}
public void run(){
try {
//接收数据
InputStream is = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String message;
while((message = br.readLine())!=null){
System.out.println("客户端:IP:"+socket.getInetAddress()+" PORT"+socket.getPort()+": "+message);
}//while
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{try {
socket.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}}
}
}
}
用Runnable,然后用线程池:
package day07;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import day07.ThreadServer2.hthread;
public class ThreadciServer {
private ServerSocket server;
private ExecutorService threadPool;
public ThreadciServer(){
try {
server = new ServerSocket(8088);
//创建线程池,最多运行50个
threadPool =Executors.newFixedThreadPool(2);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void start(){
while(true){
System.out.println("服务器等待连接.....");
try {
Socket socket = server.accept();
//调用线程,传入socket
Runnable ht1 = new hthread(socket);
threadPool.execute(ht1);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}//catch
}//while
}//start
public static void main(String[] args) {
//直接创建启动服务器
new ThreadServer2().start();
}
//线程类,在start中调用线程,并传入socket
class hthread implements Runnable {
private Socket socket;
public hthread (Socket socket){
this.socket = socket;
System.out.println("客户端连接成功!");
}
public void run(){
try {
//接收数据
InputStream is = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String message;
while((message = br.readLine())!=null){
System.out.println("客户端:IP:"+socket.getInetAddress()+" PORT"+socket.getPort()+": "+message);
}//while
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{try {
socket.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}}
System.out.println("一个客户端下线了....");
}
}
}
又小结:
返回的,server的直接就定义输出流,然后在run中原来是把信息输出在显示屏,直接就pw.println(); 发送过去。
然后在client就复杂些。不是只客户端发,然后服务器收,在返回给客户端,要无论客户端是否发,有信息就发给客户端,那么就要定义个输入流。但是单独给他创建个线程,收和发不是同步的。线程就是和服务器的接收一样的。在start方法中调用创建线程。
server:
package day07;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import day07.ThreadciServer.hthread;
public class Server_fanui {
private ServerSocket server;
private ExecutorService threadPool;
public Server_fanui(){
try {
server = new ServerSocket(8088);
//创建线程池,最多运行50个
threadPool =Executors.newFixedThreadPool(50);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void start(){
while(true){
System.out.println("服务器等待连接.....");
try {
Socket socket = server.accept();
//调用线程,传入socket
Runnable ht1 = new hthread(socket);
threadPool.execute(ht1);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}//catch
}//while
}//start
public static void main(String[] args) {
//直接创建启动服务器
new ThreadServer2().start();
}
//线程类,在start中调用线程,并传入socket
class hthread implements Runnable {
private Socket socket;
public hthread (Socket socket){
this.socket = socket;
System.out.println("客户端连接成功!");
}
public void run(){
try {
OutputStream os = socket.getOutputStream();
OutputStreamWriter osr = new OutputStreamWriter(os);
PrintWriter pw = new PrintWriter(osr,true);
//接收数据
InputStream is = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String message;
while((message = br.readLine())!=null){
// System.out.println("客户端:IP:"+socket.getInetAddress()+" PORT"+socket.getPort()+": "+message);
pw.println(message);
}//while
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{try {
socket.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}}
System.out.println("一个客户端下线了....");
}
}
}
client:
package day07;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;
import day07.Client.Getserver;
public class Client_fanhui {
//Socket,用于连接服务端的ServerSocket
private Socket socket;
//构造方法,初始化client
public Client_fanhui() {
try {
/*
* 创建Socket对象时,就会尝试根据给定的地址与端口连接服务器。所以,
* 若该对象创建成功,说明与服务端连接正常
*/
socket = new Socket("127.0.0.1",8088);
System.out.println("成功连接服务端");
} catch (UnknownHostException e) {
//端口号异常
e.printStackTrace();
} catch (IOException e) {
System.out.println("客户端创建失败");
e.printStackTrace();
}
}
//客户端启动方法
public void start(){
try{
Runnable rserver = new Getserver();
Thread tserver = new Thread(rserver);
tserver.start();
/*
* 可以通过Socket的getOutputStream()
* 方法获取一条输出流,用户与信息发送至服务端
* 是一个字节流,
*/
OutputStream out = socket.getOutputStream();
//转换成字符流,写更容易些。
OutputStreamWriter osw = new OutputStreamWriter(out);
//在包装上缓冲流 以行为单位写字符串
PrintWriter pw = new PrintWriter(osw,true);
while(true){
Scanner scanner = new Scanner(System.in);
pw.println(scanner.next());
//不写的话就不会显示,因为是缓冲流。并没有输出。自己flush一下。
}
}catch(Exception e){
e.printStackTrace();
}
}
public static void main(String[] args) {
Client client = new Client();
client.start();
}
//创建个线程,一个是原来的向服务器发数据,这个是向服务器接收数据。
class Getserver implements Runnable{
@Override
public void run() {
try{
System.out.println("6666");
InputStream is = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String message=null;
while((message=br.readLine())!=null){
System.out.println("服务器说:"+message);
}
}catch(Exception e){
e.printStackTrace();
}
}//run
}//class_getserver
}
若创建个多人同时在线聊天的方法
把别的client即在服务器是不同的线程的如何发给别的创建一个公共的list,来保存所有客户端输出流。list中放的是流。所以定义的时候 List,在构造函数中初始化。
将该客户端的输出存在在共享集合,以便使得该客户端也能接受服务器转发的消息
append(pw)
集合中存的是流!!!不是流里面的字符串!!!!!一个client对应一个list中的流。。。。
如果client断开了就要把他在list中对应的流也删除。remove(pw)
统计当前在线人数。。。list的size就是
将一个客户端发来得消息,变量list 中所有流,都发过去。实现群聊
但是会有多线程同时操作 共享集合list 。。。 线程安全问题
遍历这个集合,是迭代器,不是线程安全的,遍历与add和remove不是同步的。
要保证遍历的时候也是同步的,三个都是同步的。
要是互斥的。那就同步锁 也叫互斥锁。
我们调用静态方法的时候,不需要先生成一个实例,可以通过类名直接调用。
静态方法和静态属性为所有实例共用
因为多个线程操作一个list会造成线程不安全的问题(增加和删除,遍历没事)
用synchronized
若两个线程看到的是一个synchronized 是同步锁,不能同时访问
但是这种就是互斥,不同的地方锁住同一个对象,就是互斥锁了
可以把输出流放入map中,给每个输出流定义个名称,
然后可以根据@昵称,然后去map查找就去单独输出,否则就是全部输出
Udp通信
不可靠的传输协议
TCP连接上用的流。连续输出互相写
但是UDP不是保持长连接,发完就不管了
不需要等待对方,消耗少
主要学习的就是打包和拆包 ,打包发送过去,接收到后拆包
DatagramPacket
构建接收包
构造发送包
服务端接收
DatagramSocket
实例化的时候给一个端口号,还是服务端的端口要固定
receive接收数据
客户端发送
两端都是DatagramSocket
server:
package day07;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
/**
* UDP服务端
* @author Administrator
*
*/
public class UdpServer {
public void start(){
try{
/*
* 接收包的步骤
* 创建socket
* 创建一个合适大小的包
* 通过socket接收数据到包中
* 拆包取数据
*/
//服务端创建socket的时候要传参数端口
DatagramSocket server
= new DatagramSocket(8088);
//创建直接数组存储数据
byte data [] = new byte[1024];
//创建包,接收数据数据,要接收的长度
DatagramPacket recvpacket
= new DatagramPacket(
data,
data.length
);
//接收包裹,用包来装传过来的包
//注意,该方法是个阻塞方法,当客户端发过来包才能继续
server.receive(recvpacket);
//拆包,拿数据,可以这样获取,其实那个data就已经是了
//为了以后其他操作,来拆包
byte [] d = recvpacket.getData();
//获取数据的实际长度
int dlen = recvpacket.getLength();
/*还有........
* String(
* byte [] b,
* int offset,
* int len,
* String charsetName
* )
* 将给定的字节数组中,从offset处开始到len个字节,
* 再根据给定的字符集
* 转换为字符串
*
*/
String info = new String(d);
System.out.println("Udp客户端说:"+info);
}catch(Exception e){
e.printStackTrace();
}
}
public static void main(String[] args) {
UdpServer server = new UdpServer();
server.start();
}
}
client:
package day07;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
/**
* UDP客户端
* @author Administrator
*
*/
public class UdpClient {
// udp客户端的启动方法
public void start(){
try{
/*
* 向服务端发送数据的步骤
* 创建好Socket(一次就行)
* 准备数据
* 创建数据包
* 将数据包存入包中(2,3是一步完成的)
* 将数据包通过socket发送到服务端
*/
DatagramSocket client = new DatagramSocket();
String str = "你好!服务端";
//创建了数据,还是要把数据转换成字节才能发送
byte[] data = str.getBytes();
//打包:准备包裹,填写地址,装入数据
//地址
InetAddress address
= InetAddress.getByName("localhost");
//端口
int port = 8088;
//创建发送包
//数据,数据长度,ip,端口
DatagramPacket sendPack
= new DatagramPacket(
data,
data.length,
address,
port
);
//将数据包发送出去
client.send(sendPack);
}catch(Exception e){
e.printStackTrace();
}
}
public static void main(String[] args) {
UdpClient client = new UdpClient();
client.start();
}
}
服务端和客户端回复的
server:
package day07;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
/**
* UDP服务端
* @author Administrator
*
*/
public class UdpServer {
public void start(){
try{
/*
* 接收包的步骤
* 创建socket
* 创建一个合适大小的包
* 通过socket接收数据到包中
* 拆包取数据
*/
//服务端创建socket的时候要传参数端口
DatagramSocket server
= new DatagramSocket(8088);
//创建直接数组存储数据
byte data [] = new byte[1024];
//创建包,接收数据数据,要接收的长度
DatagramPacket recvpacket
= new DatagramPacket(
data,
data.length
);
//接收包裹,用包来装传过来的包
//注意,该方法是个阻塞方法,当客户端发过来包才能继续
server.receive(recvpacket);
//拆包,拿数据,可以这样获取,其实那个data就已经是了
//为了以后其他操作,来拆包
byte [] d = recvpacket.getData();
//获取数据的实际长度
int dlen = recvpacket.getLength();
/*还有........
* String(
* byte [] b,
* int offset,
* int len,
* String charsetName
* )
* 将给定的字节数组中,从offset处开始到len个字节,
* 再根据给定的字符集
* 转换为字符串
*
*/
String info = new String(d);
System.out.println("Udp客户端说:"+info);
//回复客户端
String str = "你好!客户端";
//创建了数据,还是要把数据转换成字节才能发送
byte[] data2 = str.getBytes();
//打包:准备包裹,填写地址,装入数据
//地址
InetAddress address
= recvpacket.getAddress();
//端口
int port = recvpacket.getPort();
//创建发送包
//数据,数据长度,ip,端口
DatagramPacket sendPack
= new DatagramPacket(
data2,
data2.length,
address,
port
);
//将数据包发送出去
server.send(sendPack);
}catch(Exception e){
e.printStackTrace();
}
}
public static void main(String[] args) {
UdpServer server = new UdpServer();
server.start();
}
}
client:
package day07;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
/**
* UDP客户端
* @author Administrator
*
*/
public class UdpClient {
// udp客户端的启动方法
public void start(){
try{
/*
* 向服务端发送数据的步骤
* 创建好Socket(一次就行)
* 准备数据
* 创建数据包
* 将数据包存入包中(2,3是一步完成的)
* 将数据包通过socket发送到服务端
*/
DatagramSocket client = new DatagramSocket();
String str = "你好!服务端";
//创建了数据,还是要把数据转换成字节才能发送
byte[] data = str.getBytes();
//打包:准备包裹,填写地址,装入数据
//地址
InetAddress address
= InetAddress.getByName("localhost");
//端口
int port = 8088;
//创建发送包
//数据,数据长度,ip,端口
DatagramPacket sendPack
= new DatagramPacket(
data,
data.length,
address,
port
);
//将数据包发送出去
client.send(sendPack);
//接收服务端的数据
byte data2 [] = new byte[1024];
//创建包,接收数据数据,要接收的长度
DatagramPacket recvpacket
= new DatagramPacket(
data2,
data2.length
);
//接收包裹,用包来装传过来的包
//注意,该方法是个阻塞方法,当客户端发过来包才能继续
client.receive(recvpacket);
//拆包,拿数据,可以这样获取,其实那个data就已经是了
//为了以后其他操作,来拆包
byte [] d = recvpacket.getData();
//获取数据的实际长度
int dlen = recvpacket.getLength();
/*还有........
* String(
* byte [] b,
* int offset,
* int len,
* String charsetName
* )
* 将给定的字节数组中,从offset处开始到len个字节,
* 再根据给定的字符集
* 转换为字符串
*
*/
String info = new String(d);
System.out.println("Udp服务端说:"+info);
}catch(Exception e){
e.printStackTrace();
}
}
public static void main(String[] args) {
UdpClient client = new UdpClient();
client.start();
}
}
我的eclipse出现了问题。。。总是运行之前的代码。上网各种查解决方法。。。全是了。。。还是不行。灵异了。