스스로 자바 버전 QQ를 작성합니다
57212 단어 JAVAFX
프로그램이 하루 만에 완성되어 많은 테스트를 하지 않았기 때문에 BUG는 많은 다운로드 주소를 가지고 있다.https://github.com/EnTaroAdunZ/LAN_QQ/
1.1.어떻게 써요?
1.1.1.서버 열기
그림에서 보듯이 서비스 측 주소는 본 기기의 주소(본 프로그램에 세그먼트, 네트워크 카드 등 관련 선택 설정이 부족하기 때문에 세그먼트를 수정하려면 QQServer의Main류에서 getServerIp로 수정해야 함)를 나타낸다. 기본적으로 서버 주소는 본 기기의 모든 네트워크 카드의 세그먼트를 훑어보고 172로 시작하는 세그먼트를 서비스 측 IP 주소로 선택한다.현재 온라인 인원수와 현재 온라인 목록은 현재 연결된 클라이언트에 따라 동적 업데이트됩니다.프로그램이 더욱 잘 통용될 수 있도록 이번 실험에서 hsqldb를 데이터베이스로 선택했고dao층을 분리했다. 데이터 원본을 바꾸려면 데이터 원본과 계정 비밀번호를 바꾸면 된다.데이터베이스 데이터를 수정하려면 Main 클래스에서 initData를 수정하면 됩니다.1.1.2.첫 번째 클라이언트를 열고 서버 주소를 수정한다. 그림에서 보듯이 이 인터페이스는 클라이언트의 로그인 인터페이스이다. 이 인터페이스에서 고객은 계정 비밀번호와 서버 주소를 기입해야 한다. 서버 주소는 서버에서 제공한다.로그인을 클릭하면 서버에 메시지를 보냅니다. 서버는 계정 비밀번호가 맞는지 확인합니다. 계정 비밀번호가 틀리거나 연결이 실패하면 알림을 주고 로그인 인증이 성공하면 친구 목록 인터페이스에 들어갑니다.
1.1.3.두 번째, 세 번째 클라이언트에 로그인
1.1.4.친구 리스트 인터페이스에 입장
그림에서 보듯이 이 인터페이스는 주로 두 부분으로 나뉘는데 윗부분에 이미지가 표시되고 현재 로그인한 사용자의 닉네임, IP, 포트 등은 사용자 이미지가 본 실험과 관계가 크지 않기 때문에 무작위로 선택한 방법으로 데이터베이스에 이미지 URL 등을 저장하지 않았기 때문에 세 클라이언트의 이미지가 일치하지 않는다.다음 그림은 이 서버의 상태입니다.
1.1.5.채팅 창
캐릭터 이미지나 대응열을 두 번 클릭하면 채팅창을 열 수 있습니다(같은 캐릭터는 한 포트만 열 수 있습니다).그림은 텐센트에서 아리와 이야기를 나누고 있다.
채팅창은 세 부분으로 나뉘는데 상부는 상대방의 닉네임을 표시하고 중부는 파일 전송 기능 입구와 같은 관련 부가 기능이다.하부는 핵심 부분, 문자 통신 부분으로 채팅 기록, 발송 대기구역, 발송 버튼, QQ쇼 부분으로 나뉘는데 QQ쇼는 관련 기록 기능이 없기 때문에 고정적이다.
1.1.6.메시지 보내기
메시지를 보내는 것은 주로 두 가지 상황으로 나뉜다. 첫 번째는 상대방도 채팅창을 열었고, 두 번째는 상대방이 창을 열지 않았을 때 상대방의 이미지가 알림을 울린다.첫 번째 상황: 두 번째 상황:
1.1.7.파일 전송
발송자: 발송자가 필요한 창을 열고 전송 파일을 클릭하며 필요한 파일을 클릭하면 발송을 완성할 수 있습니다.
실패할 경우 발송자는 알림을 주고, 성공하면 기본 알림을 줍니다.동시에 수신자는 파일을 받아들일지 안 받아들일지를 묻는 창을 팝업하고 받아들일 경우 파일을 저장할 위치를 선택할 수 있으며, 그렇지 않으면 저장을 포기할지 여부를 알립니다.
2. 핵심 코드
2.1.서비스 단말기
2.1.1.연결 데이터베이스
이번 실험에서 JDBC를 사용하여 연결을 진행하였는데, 지구화 도구를 사용하지 않았기 때문에 가장 원시적인 SQL 방식을 사용하였으며, 실제 사용 과정(mysql 등으로 바꾸었음)에서 TABLE 를 수정하기만 하면NAME 및 jdbc 드라이브를 사용하고 계정 비밀번호를 추가하면 변경 사항이 완료됩니다.
public class UserDao {
String TABLE_NAME="user";
String ID="account";
String CREATE_SQL ="(account varchar(254),password varchar(254),user_name varchar(254))";
String INSERT_SQL = "insert into "+TABLE_NAME+" values(?,?,?)" ;
String SELECT_SQL = "select * from "+TABLE_NAME ;
String SELECT_SQL_ID = "select * from "+TABLE_NAME+" where "+ID+"=?" ;
String DELETE_SQL="delete from "+TABLE_NAME+" where "+ID+"=?";
private UserDao() {}
private static UserDao single=null;
public static UserDao getInstance() {
if (single == null) {
single = new UserDao();
}
return single;
}
public void createData(){
try {
Class.forName("org.hsqldb.jdbcDriver");
} catch (Exception e) {
e.printStackTrace();
}
try(Connection connection = DriverManager.getConnection("jdbc:hsqldb:mem:aname", "sa", "");
Statement stmt = connection.createStatement();)
{
String sql1 = "create table IF NOT EXISTS "+TABLE_NAME+CREATE_SQL;
stmt.executeUpdate(sql1);
} catch (Exception e) {
e.printStackTrace();
}
}
public void insert(User user) {
try {
createData();
Class.forName("org.hsqldb.jdbcDriver");
} catch (Exception e) {
e.printStackTrace();
}
try(Connection connection = DriverManager.getConnection("jdbc:hsqldb:mem:aname", "sa", "");
PreparedStatement ps = connection.prepareStatement(INSERT_SQL))
{
ps.setString(1,user.getAccount());
ps.setString(2,user.getPassword());
ps.setString(3,user.getUserName());
ps.execute();
} catch (Exception e) {
e.printStackTrace();
}
}
public User selectByAccount(String account) {
}
public List selectAll() {
}
}
2.1.2.데이터 초기화
이 곳에서 보여준dao의 사용법은 jvm 메모리 데이터베이스를 계속 사용하면 여기에 추가해야 할 데이터를 추가할 수 있습니다.
public static void initData(){
UserDao userDao=UserDao.getInstance();
userDao.insert(new User("ztf1","ztf1"," "));
userDao.insert(new User("ztf2","ztf2"," "));
userDao.insert(new User("ztf3","ztf3"," "));
userDao.insert(new User("ztf4","ztf4"," "));
userDao.insert(new User("ztf5","ztf5"," "));
}
2.1.3.서버 IP 찾기
모든 네트워크 카드를 가져오고 세그먼트 주소를 가져옵니다. 172가 시작되면 서버 IP 주소로 설정하여 교내 통신을 실현합니다. 다른 사용 장면은 ipconfig나ifconfig를 통해 세그먼트를 보고 starts 값을 구체적으로 설정할 수 있습니다.
public static void getServerIp(){
try {
Enumeration interfaces=null;
interfaces = NetworkInterface.getNetworkInterfaces();
while (interfaces.hasMoreElements()) {
NetworkInterface ni = interfaces.nextElement();
Enumeration addresss = ni.getInetAddresses();
while(addresss.hasMoreElements())
{
InetAddress nextElement = addresss.nextElement();
String hostAddress = nextElement.getHostAddress();
if(hostAddress.startsWith("172")){
ServerMessage.SERVER_IP=hostAddress;
System.out.println(" IP :" +hostAddress);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
2.1.4.클라이언트 대응 Bean
유일무이한 클라이언트를 어떻게 정의합니까: IP 주소 + 포트 번호 + 이 사용자의 계정
public class Link implements Serializable {
private User user;
private String port;
private String ip;
@Override
public boolean equals(Object obj) {
Link other = (Link) obj;
return this.user.equals(other.getUser());
}
@Override
public int hashCode() {
return user.hashCode();
}
}
2.1.5.데이터베이스 대응 PO
데이터베이스에 대응하는 모델.
public class User implements Serializable {
private String account;
private String password;
private String userName;}
2.1.6.서버 클라이언트 전송 대상
그 중에서 Type은 현재 전송 내용이 어떤 의미를 대표하는지 정의했다. 클라이언트든 서버든 type에 의존하여 msg가 어떤 의미가 있는지 판단한다. CTC는 클라이언트를 클라이언트로 대표하고 CTS는 클라이언트를 서버로 대표하며 STC는 서버에서 클라이언트로 대표한다. 현실 장면에서 STS가 존재해야 한다. 즉, 여러 대의 서버가 서로 전송하는 상황이 존재해야 한다.그 중에서portList는 현재 서버의 온라인 목록 상황을 대표하고 이를 통해 온라인 목록을 방송합니다.
public class Msg implements Serializable {
public enum Type {
CTC,CTS_SENDPORT,STC_SENDPORT,CTS_LOGIN,CTS_LOGOUT,CTC_SENDFILE
}
private Type type;
private String msg;
private User user;
private Link link;
private File file;
private ArrayList portList;
public static Msg getCTC_SENDFILE(Link link,File file) {
return new Msg(Type.CTC_SENDFILE,link,file);
}
public static Msg getSTCMsg_sendPortToC(ArrayList linkList) {
return new Msg(Type.STC_SENDPORT,linkList);
}
public static Msg getCTCMsg(String p_msg,String userName) {
return new Msg(Type.CTC,p_msg,userName);
}
public static Msg getCTCMsg_CTS_LOGOUT(Link link) {
return new Msg(Type.CTS_LOGOUT,link);
}
public static Msg getCTSMsg_sendPortToS(String port) {
return new Msg(Type.CTS_SENDPORT,port);
}
public static Msg getSTC_LOGIN(String status,User user) {
return new Msg(Type.CTS_LOGIN,status,user);
}
public static Msg getCTSMsg_LOGIN(User user,Integer port) {
return new Msg(Type.CTS_LOGIN,String.valueOf(port),user);
}
public Msg(Type type,Link link, File file) {
super();
this.type = type;
this.link=link;
this.file = file;
}
public Msg(Type type, Link link) {
super();
this.type = type;
this.link = link;
}
public Msg(Type type, String msg,User user) {
super();
this.type = type;
this.msg = msg;
this.user=user;
}
public Msg(Type type, String msg) {
super();
this.type = type;
this.msg = msg;
}
public Msg(Type type, String msg, String userName) {
super();
this.type = type;
this.msg = msg;
this.user=new User(null,null,userName);
}
public Msg(Type type, ArrayList linkList) {
super();
this.type = type;
this.portList = linkList;
}
}
2.1.7.서버 모니터
서버는 특정 포트를 감청하여 라인을 열어 정보를 받습니다.
public class ServerService implements Runnable{
@Override
public void run() {
try (ServerSocket serverSocket=new ServerSocket(ServerMessage.SERVER_PORT)){
while(true) {
Socket socket=serverSocket.accept();
// SocketHandle
ServerReceiveHandle handle=new ServerReceiveHandle(socket);
Thread thread=new Thread(handle);
thread.start();
}
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(" !");
}
}
}
2.1.8.서버 핵심 처리 코드
본 프로그램의 C-S 구조에서 서비스 측은 그 어떠한 C-C에서 정보 교류의 전송도 책임지지 않고 온라인 목록의 유지 보수만 책임진다. 이는 로그인의 검사, 로그인 결과 반환, 이상 퇴출 처리, 정상적인 퇴출 처리, 이런 처리는 당연히 인터페이스의 업데이트도 포함한다.주의해야 할 것은 이 스레드는javafx스레드가 아니기 때문에 인터페이스의 업데이트는 ui스레드 처리에 전달해야 한다.
public class ServerReceiveHandle implements Runnable {
private Socket socket;
public ServerReceiveHandle(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
Link link = null;
try {
//
ObjectInputStream is = new ObjectInputStream(socket.getInputStream());
ObjectOutputStream os = new ObjectOutputStream(socket.getOutputStream());
Msg msg = (Msg) is.readObject();
if (msg.getType() == Msg.Type.CTS_LOGIN) {
//
UserDao userDao = UserDao.getInstance();
User user = userDao.selectByAccount(msg.getUser().getAccount());
if (user == null || !user.getPassword().equals(msg.getUser().getPassword())) {
//
os.writeObject(Msg.getSTC_LOGIN("101", null));
os.writeObject(null);
} else {
//
ArrayList linkList = ServerMessage.linkList;
if (!linkList.contains(new Link(msg.getUser()))) {
link = new Link(user, msg.getMsg(), socket.getInetAddress().getHostAddress());
linkList.add(link);
Link finalLink = link;
// UI
Platform.runLater(
() -> {
MainUI.online_userList.add(finalLink.toString());
MainUI.updateOnlineNum(String.valueOf(linkList.size()));
}
);
os.writeObject(Msg.getSTC_LOGIN("100", user));
os.writeObject(null);
new Thread(() -> {
for (Link link12 : linkList) {
//
new Thread(new STCSendPortHandle(link12.getIp(), link12.getPort(), Msg.getSTCMsg_sendPortToC(linkList))).start();
}
}).start();
} else {
//
os.writeObject(Msg.getSTC_LOGIN("102", null));
os.writeObject(null);
}
}
}else if (msg.getType() == Msg.Type.CTS_LOGOUT) {
Link tempLink = msg.getLink();
Platform.runLater(
() -> {
MainUI.online_userList.remove(tempLink.toString());
}
);
ArrayList<Link> linkList = ServerMessage.linkList;
linkList.remove(tempLink);
//
Platform.runLater(
() -> {
MainUI.updateOnlineNum(String.valueOf(ServerMessage.linkList.size()));
}
);
new Thread(() -> {
for (Link link1 : linkList) {
//
new Thread(new STCSendPortHandle(link1.getIp(), link1.getPort(), Msg.getSTCMsg_sendPortToC(linkList))).start();
}
}).start();
}
} catch (SocketException e) {
e.printStackTrace();
if(link!=null){
//
Link finalLink = link;
Platform.runLater(
() -> {
MainUI.online_userList.remove(finalLink.toString());
}
);
ArrayList<Link> linkList = ServerMessage.linkList;
linkList.remove(link);
//
Platform.runLater(
() -> {
MainUI.updateOnlineNum(String.valueOf(ServerMessage.linkList.size()));
}
);
new Thread(() -> {
for (Link link1 : linkList) {
//
new Thread(new STCSendPortHandle(link1.getIp(), link1.getPort(), Msg.getSTCMsg_sendPortToC(linkList))).start();
}
}).start();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
2.2.클라이언트
2.2.1.로그인 인터페이스 제어
로그인 과정에서 스레드 탱크의 스레드를 통해 서버에 정보를 보내서 로그인 정보를 검증하고 서버가 정보를 되돌려준 후 이 정보를 통해 로그인할 수 있거나 오류 정보를 팝업할 수 있는지 여부를 결정한다.
public class MainController implements Initializable {
@FXML
public TextField tf_serverIP;
@FXML
public PasswordField pf_password;
@FXML
public TextField tf_account;
ExecutorService es = null;
@FXML
public void loadOn(ActionEvent event){
try {
String account=tf_account.getText();
String password=pf_password.getText();
String serverIP=tf_serverIP.getText();
Future future =es.submit(new LoadOnServer(account,password,serverIP));
Status status = future.get();
if(status==Status.Account_Password_Error){
Util.alertInformationDialog(" ",status.getMsg());
}else if(status==Status.Connection_Error){
Util.alertInformationDialog(" ",status.getMsg());
}else if(status==Status.Success){
Window window = ((Node) (event.getSource())).getScene().getWindow();
window.hide();
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/list.fxml"));
Parent root = loader.load();
Scene scene = new Scene(root);
Stage stg=new Stage();
stg.setTitle(ClientMessage.USER_NAME +" QQ");
stg.setOnCloseRequest(event1 -> {
new Thread(new LogOutServer()).start();
try {
Thread.sleep(500);
}catch (Exception e){
e.printStackTrace();
}
System.exit(0);
});
stg.setScene(scene);
stg.show();
ChatListController chatListController= (ChatListController) loader.getController();
chatListController.setValue(stg);
}
}catch (Exception e){
e.printStackTrace();
}
}
@Override
public void initialize(URL location, ResourceBundle resources) {
es = Executors.newSingleThreadExecutor();
new Thread(new ReceiveServer()).start();
}
}
2.2.2.전역 클래스
ClientMessage: 현재 클라이언트에 대한 정보 저장ServerMessage: 현재 서버에 대한 정보를 저장Status: Server 층이 Controller에게 되돌려주는 상태 코드 정보, 인터페이스는 이를 바탕으로 후속 UI 업데이트 작업을 판단합니다. Util: 일반적인 도구 클래스
2.2.3.친구 목록 양방향 귀속 데이터 모델
이 클래스는 UI층의listview와 연결되어 있으며 이 모델을 바꾸어 친구 목록의 상태를 동적 업데이트합니다. 온라인 업데이트, 오프라인 업데이트, 이미지 깜빡임 등을 포함하고 현재 창의 동태를 기록하여 여러 번 창을 열지 않도록 합니다.
public class ChatListModel implements Serializable {
private final StringProperty userName;
private final StringProperty ip;
private final StringProperty port;
//
private final StringProperty headPortrait;
//
private final StringProperty msgFlag;
//
private final StringProperty flicker;
//
private final StringProperty init;
//
private final StringProperty openFlag;
//
private String message;
private ChatWinController chatWinController;
{
this.msgFlag = new SimpleStringProperty("false");
this.flicker = new SimpleStringProperty("false");
this.init=new SimpleStringProperty("false");
this.headPortrait=new SimpleStringProperty("null");
this.openFlag=new SimpleStringProperty("false");
message=new String();
chatWinController=null;
}
public ChatListModel(String userName, String ip, String port ) {
this.userName = new SimpleStringProperty(userName);
this.ip = new SimpleStringProperty(ip);
this.port = new SimpleStringProperty(port);
}
@Override
public String toString() {
return "ChatListModel{" +
"userName=" + userName +
", ip=" + ip +
", port=" + port +
", headPortrait=" + headPortrait +
'}';
}
}
2.2.4.친구 목록 컨트롤러
이 컨트롤러는 주로 채팅창 열기, 친구 리스트 업데이트, 파일 수용 기능 등을 완성한다.목록 창이 열리면 파일을 받아들일 수 있다는 얘기다.이 유형의 주요 난점은 이미지가 깜빡이는 실현에 있다. 내 방법은 클라이언트가 정보를 받은 후에 이 정보가 다른 클라이언트에서 왔다고 판단하면 이 정보에 대응하는 발송자를 찾고 그에 대응하는 데이터 모델을 찾아 이 데이터 모델을 업데이트한 다음에 라인을 열고 이 라인은 간격을 두고 몇 개의 변수를 수정한다. 이 몇 개의 변수를 통해 이미지의 주소(하나는 공백 이미지)를 수정하고 UI를 갱신한다.이것은 깜빡이는 효과를 냅니다. 마지막으로 창을 열면 공유 변수를 통해 이 라인을 종료합니다.
public class ChatListController implements Initializable {
@FXML
public ImageView iv_head;
@FXML
public Label lb_name;
@FXML
public Label lb_ip;
@FXML
public Label lb_port;
@FXML
ListView lv_chatList;
private Window window;
public void setValue(Window window){
this.window=window;
}
private final Image IMAGE1 = new Image("http://othgjp7hs.bkt.clouddn.com/18-6-8/2780393.jpg");
private final Image IMAGE2 = new Image("http://othgjp7hs.bkt.clouddn.com/18-6-8/36061669.jpg");
private final Image IMAGE3 = new Image("http://othgjp7hs.bkt.clouddn.com/18-6-8/35725819.jpg");
private final Image IMAGE4 = new Image("http://othgjp7hs.bkt.clouddn.com/18-6-8/24494195.jpg");
private final Image IMAGE5 = new Image("http://othgjp7hs.bkt.clouddn.com/18-6-8/51868892.jpg");
private final Image IMAGE6 = new Image("http://othgjp7hs.bkt.clouddn.com/18-6-8/16875648.jpg");
private final Image IMAGEWHITE = new Image("http://othgjp7hs.bkt.clouddn.com/18-6-8/75659527.jpg");
private Image[] listOfImages = {IMAGE1, IMAGE2, IMAGE3,IMAGE4,IMAGE5,IMAGE6};
@Override
public void initialize(URL location, ResourceBundle resources) {
ClientMessage.chatListController=this;
int index = new Random().nextInt(listOfImages.length);
iv_head.setImage(listOfImages[index]);
iv_head.setFitHeight(100);
iv_head.setFitWidth(100);
lb_name.setText(ClientMessage.USER_NAME);
lb_ip.setText(ClientMessage.Client_IP);
lb_port.setText(String.valueOf(ClientMessage.Client_PORT));
ServerMessage.lv_chatList=lv_chatList;
lv_chatList.setItems(ServerMessage.items);
lv_chatList.setOnMouseClicked(click -> {
if (click.getClickCount() == 2) {
try {
ChatListModel selectedItem = lv_chatList.getSelectionModel()
.getSelectedItem();
if(selectedItem.getOpenFlag().equals("false")){
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/chatWin.fxml"));
Parent root = loader.load();
Scene scene = new Scene(root);
Stage stg=new Stage();
stg.setTitle(ClientMessage.USER_NAME+" ");
stg.setScene(scene);
selectedItem.setOpenFlag("true");
selectedItem.setMsgFlag("false");
stg.setOnCloseRequest(new EventHandler() {
@Override
public void handle(WindowEvent event) {
selectedItem.setOpenFlag("false");
}
});
stg.show();
ChatWinController chatWinController= (ChatWinController) loader.getController();
chatWinController.setVavlue(selectedItem.getUserName(),selectedItem.getIp(),selectedItem.getPort(),stg);
}
}catch (Exception e){
e.printStackTrace();
}
}
});
lv_chatList.setCellFactory(param -> new ListCell() {
private ImageView imageView = new ImageView();
@Override
public void updateItem(ChatListModel chatListModel, boolean empty) {
super.updateItem(chatListModel, empty);
if (empty) {
setText(null);
setGraphic(null);
} else {
if(chatListModel.getInit().equals("false")){
chatListModel.setInit("ture");
int index = new Random().nextInt(listOfImages.length);
imageView.setImage(listOfImages[index]);
chatListModel.setHeadPortrait(String.valueOf(index));
imageView.setFitHeight(56);
imageView.setFitWidth(56);
setText(chatListModel.getUserName());
setGraphic(imageView);
}else{
if(chatListModel.getMsgFlag().equals("true")){
if(chatListModel.getFlicker().equals("true")){
imageView.setImage(IMAGEWHITE);
imageView.setFitHeight(56);
imageView.setFitWidth(56);
setText(chatListModel.getUserName());
setGraphic(imageView);
}else{
normalHeadPortrait(chatListModel);
}
}else{
normalHeadPortrait(chatListModel);
}
}
}
}
private void normalHeadPortrait(ChatListModel chatListModel) {
imageView.setImage(listOfImages[Integer.valueOf(chatListModel.getHeadPortrait())]);
imageView.setFitHeight(56);
imageView.setFitWidth(56);
setText(chatListModel.getUserName());
setGraphic(imageView);
}
});
}
public void saveFile(Link link, File file){
Platform.runLater(() -> {
boolean result = Util.alertChooseDialog(" "
, " " + link.getIp() + " " + link.getUser().getUserName() + " , ?");
if(result){
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle(" ");
fileChooser.setInitialFileName(file.getName());
File saveFile = fileChooser.showSaveDialog(window);
new Thread(()->{
try (FileInputStream fileInputStream=new FileInputStream(file);
FileOutputStream fileOutputStream=new FileOutputStream(saveFile)){
byte[] b = new byte[2048];
int read;
while ((read = fileInputStream.read(b)) != -1) {
fileOutputStream.write(b,0,read);
}
}catch (Exception e){
e.printStackTrace();
}
}).start();
}else{
Util.alertInformationDialog(" "," " + link.getIp() + " " + link.getUser().getUserName()+" ");
}
});
}
}
2.2.5.채팅 창
채팅창 컨트롤러는 주로 파일 전송, 정보 발송 등의 기능을 실현한다.파일 전송은 새로운 스레드를 켜서 전송함으로써 UI 인터페이스의 막힘을 피하지만 정보 전송이 성공했다는 알림과 전송 진도 표시를 완성하지 못한 것은 불완전한 것이다.정보 발송도 상대적으로 간단하다. UI의 조율을 완성하면 된다. 물론 발송의 성공 여부에 대한 힌트도 있다. 비교적 어려운 점은 채팅 기록의 업데이트와 저장이다. 데이터베이스를 디자인할 때 나는 채팅 기록의 저장을 고려하지 않았기 때문에 후속 디자인에서 이 정보를 메모리에 저장하는 것을 선택했기 때문에 다음 시작에 잃어버리는 것은 미비하다.만약 상대방이 창을 열지 않았다면, 창을 연 상태와 구분해야 한다.그중에도 비교적 번거로운 문제가 존재한다. 예를 들어 정보를 보내는 것과 받는 순서가 어지럽지 않을까?서버 측의 다운타임에 대해 상대방이 이미 오프라인 상태인데, 본 컴퓨터는 여전히 온라인으로 표시됩니다. 이런 상황은 어떻게 처리합니까?오프라인에서 정보를 받으면 어떻게 처리합니까?
public class ChatWinController implements Initializable {
private final Image IMAGE1 = new Image("http://othgjp7hs.bkt.clouddn.com/18-6-8/85814151.jpg");
private final Image IMAGE2 = new Image("http://othgjp7hs.bkt.clouddn.com/18-6-8/86481160.jpg");
private final Image IMAGE3 = new Image("http://othgjp7hs.bkt.clouddn.com/18-6-8/5502061.jpg");
private final Image IMAGE4 = new Image("http://othgjp7hs.bkt.clouddn.com/18-6-8/92563047.jpg");
private final Image IMAGE5 = new Image("http://othgjp7hs.bkt.clouddn.com/18-6-8/81909198.jpg");
@FXML
public ImageView iv_file;
@FXML
public Label lb_name;
@FXML
public ImageView iv_sound;
@FXML
public ImageView iv_video;
@FXML
public ImageView iv_show1;
@FXML
public ImageView iv_show2;
@FXML
public TextArea ta_wait_sendMessage;
@FXML
public TextArea ta_message;
private Window window;
SimpleDateFormat sdf = new SimpleDateFormat("yyyy MM dd HH:mm:ss");
private String name;
private String port;
private String ip;
ExecutorService es =null;
public void setVavlue(String name,String ip,String port,Window window){
this.name=name;
this.port=port;
this.ip=ip;
this.window=window;
lb_name.setText(name);
loadMsg();
}
@Override
public void initialize(URL location, ResourceBundle resources) {
iv_file.setImage(IMAGE1);
iv_video.setImage(IMAGE2);
iv_sound.setImage(IMAGE3);
iv_show1.setImage(IMAGE4);
iv_show2.setImage(IMAGE5);
es = Executors.newSingleThreadExecutor();
}
@FXML
public void sendFile(MouseEvent mouseEvent) {
try {
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle(" ");
File file = fileChooser.showOpenDialog(window);
Future submit = es.submit(new SendFileServer(ClientMessage.Client_IP, Integer.valueOf(port), ClientMessage.USER_NAME, file));
submit.get();
}catch (Exception e){
e.printStackTrace();
}
}
@FXML
public void sendMessage(MouseEvent mouseEvent) {
try {
String text = ta_wait_sendMessage.getText();
StringBuilder sb=new StringBuilder();
sb.append(sdf.format(new Date()));
sb.append(" "+ClientMessage.USER_NAME+"\t:
");
sb.append(text+"
");
Future future =es.submit(new SendServer(sb.toString(),ip,Integer.valueOf(port),ClientMessage.USER_NAME));
Status status = future.get();
if(status==Status.Send_Success){
ta_wait_sendMessage.setText("");
StringBuilder stringBuilder=new StringBuilder(ta_message.getText());
stringBuilder.append(sdf.format(new Date()));
stringBuilder.append(" "+ClientMessage.USER_NAME+"\t:
");
stringBuilder.append(text+"
");
ta_message.setText(stringBuilder.toString());
storeMsg(stringBuilder.toString());
}else{
Util.alertInformationDialog(" "," , !");
}
}catch (Exception e){
e.printStackTrace();
}
}
//
public void storeMsg(String message){
for( ChatListModel cm:ServerMessage.items){
if(cm.getUserName().equals(lb_name.getText())){
cm.setMessage(message);
break;
}
}
}
public void updataMsg(String append){
ta_message.setText(ta_message.getText()+append);
}
public void loadMsg(){
for( ChatListModel cm:ServerMessage.items){
if(cm.getUserName().equals(lb_name.getText())){
cm.setChatWinController(this);
ta_message.setText(cm.getMessage());
break;
}
}
}
}
2.2.6.통신 서비스
LoadOn Server: 로그인 서비스를 위한 루트 열기: 루트를 열어 종료하는 서비스 Receive Server: 루트를 열어 정보를 받는 서비스 SendFile Server: 루트를 열어 문서를 보내는 서비스 SendServer: 루트를 열어 정보를 보내는 서비스 SendServer: 루트를 열어 정보를 보내는 서비스
public class LoadOnServer implements Callable<Status> {
private String account;
private String password;
private String serverIP;
public LoadOnServer(String account, String password, String serverIP) {
this.account = account;
this.password = password;
this.serverIP = serverIP;
}
@Override
public Status call() {
try {
Socket socket=new Socket(serverIP,ServerMessage.SERVER_PORT);
ClientMessage.Client_IP=socket.getLocalAddress().toString().substring(1);
ObjectOutputStream os = new ObjectOutputStream(socket.getOutputStream());
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
os.writeObject(Msg.getCTSMsg_LOGIN(new User(account, password),ClientMessage.Client_PORT));
os.writeObject(null);
Msg msg=(Msg)ois.readObject();
if(msg.getMsg().equals("100")){
ClientMessage.USER_NAME=msg.getUser().getUserName();
ClientMessage.user=msg.getUser();
ServerMessage.SERVER_IP=serverIP;
return Status.Success;
}else if(msg.getMsg().equals("101")){
return Status.Account_Password_Error;
}else if(msg.getMsg().equals("102")){
return Status.Connection_Error;
}
}catch (Exception e){
e.printStackTrace();
}
return Status.Connection_Error;
}
}