Cómo escribir una sala de chat en Linux
Desde que comencé a aprender programación de redes Linux, quería escribir una sala de chat. Originalmente planeé escribirla en forma multiproceso, pero descubrí que la comunicación entre procesos era un poco problemática. Es caro, así que más tarde decidí usarlo. ¿Es posible utilizar subprocesos múltiples? Así que eché un vistazo al uso de subprocesos en Linux. De hecho, solo necesito saber pthread_create, así que comencé a trabajar en ello. El proceso de depuración fue bastante doloroso. Originalmente planeé usar C puro y utilicé una matriz simple para almacenar la información de conexión del cliente, pero ocurrieron algunos problemas muy extraños durante el tiempo de ejecución. Me pregunto si se accedió a recursos críticos, relacionados con la exclusión mutua. hilos, etc.; extraño Sí, el problema se resuelve cuando cambias al conjunto o mapa de STL, pero después de buscar en Internet, descubrí que STL no es seguro para subprocesos. En cuanto a cuál es el problema, no quiero. preocuparse por eso por el momento. Puede ser un error en otros pequeños detalles.
Pegue el código primero:
El primero es el archivo de encabezado necesario header.h:
#ifndef ?__HEADER_H#define ?__HEADER_H#include lt stdio.hgt; stdlib .hgt; #incluir lt; #incluir lt; unistd.hgt; #incluir lt; incluir lt; error.hgt; #incluir lt; #incluir pthread.hgt; #definir ?bool ?int ?// las 3 líneas son para c originalmente #define ?true ? 1#define ?false ?0#define ?PORT ?9003#define ?BUF_LEN ?1024 ?// tamaño del buffer #define MAX_CONNECTION ?6 ?// El número máximo de conexiones permitido por el servidor, que usted mismo puede cambiar #define ?For(i, s, t) ?for(i = (s); i != (t); i)#endif // __HEADER_H
Luego está la parte del cliente?client.cpp, que es relativamente simple:
#include "header.h"// La función de hilo del cliente para recibir mensajes void* recv_func( void *args)
{ char buf[BUF_LEN]; int sock_fd = *(int*)args; while(true) { int n = recv(sock_fd, buf, BUF_LEN, 0); ? break; ?// Esta oración es muy crítica. Al principio no sabía que podía usar esto para determinar si la comunicación había terminado. Usé algunos otros métodos extraños para finalizar y cerrar sock_fd para evitar la aparición de CLOSE_WAIT. FIN_WAIT2 estados. T.T write(STDOUT_FILENO, buf, n);
}
close(sock_fd);
exit(0); p>} // La función de procesamiento para la comunicación entre el cliente y el servidor void process(int sock_fd)
{
pthread_t td;
pthread_create(amp ; td, NULL , recv_func, (void*)amp; sock_fd);// Abre un nuevo hilo para recibir mensajes, evitando el modo original de una lectura y una escritura. De hecho, lo puse en un bucle while al principio. lo que me hizo estallar en lágrimas. . .
char buf[BUF_LEN]; while(true) { int n = read(STDIN_FILENO, buf, BUF_LEN);
buf[n ] = '\0'; al igual que la lectura estándar no tiene un terminador de cadena, debe agregarlo manualmente
send(sock_fd, buf, n, 0);
}
cerrar (sock_fd);
}int main(int argc, char *argv[])
{
assert(argc == 2); ;
bzero(amp;cli, sizeof(cli));
cli.sin_family = AF_INET;
cli.sin_addr.s_addr = htonl(INADDR_ANY) ;
cli.sin_port = htons(PORT); // Si falta htons, no se puede establecer la conexión. ¿Se debe a la máquina little-endian?
int sc = socket(AF_INET, SOCK_STREAM, 0); if(sc lt; 0) {
perror("error de socket");
salir (-1);
}
inet_pton(AF_INET, argv[1], amp; (cli.sin_addr)); // Utiliza el primer parámetro como conexión al Dirección del servidor
int err = connect(sc, (struct sockaddr*)&cli, sizeof(cli)); if(err lt; 0) {
perror("error de conexión " );
salir(-2);
}
proceso(sc);
cerrar(sc);
}
Finalmente, server.cpp:
#include lt;mapgt;#include "header.h"usando std::map
maplt; int, struct sockaddr_in*gt; calcetines; // Se utiliza para registrar cada cliente, la clave es el descriptor de archivo del socket que se comunica con el cliente y el valor es la información de sockaddr_in del cliente correspondiente. / Enviar mensajes grupales a todos los clientes en calcetines en línea void send_all(const char *buf, int len)
{ for(auto it = calcetines.begin(); it != calcetines.end(); it )
send(it-gt; first, buf, len, 0);
}//La función de hilo del servidor que recibe el mensaje void* recv_func(void* args)
{ int cfd = *(int*)args; char buf[BUF_LEN];
true) { int n = recv(cfd, buf, BUF_LEN, 0); if(n lt; = 0) // La frase clave se utiliza como juicio para finalizar la comunicación write(STDOUT_FILENO, buf, n) ; if(strcmp(buf, "bye\n") == 0) { // Si se recibe un adiós del cliente, la comunicación finaliza y el descriptor de archivo correspondiente se elimina de los calcetines. El espacio aplicado dinámicamente también debe liberarse antes. eliminación
printf("cerrar conexión con el cliente d.\n", cfd free(socks[cfd]);
socks.erase(cfd); /p >
}
send_all(buf, n); // Enviar un mensaje grupal a todos los clientes conectados}
close(cfd); con este cliente Descriptor de archivo para la comunicación con el cliente}//Función de subproceso void* proceso(void *argv) para la comunicación con un cliente
{
pthread_t td;
pthread_create(amp; td, NULL, recv_func, (void*)argv); // Abre un nuevo hilo en la función de procesamiento principal para recibir mensajes del cliente
int sc = *(int *) argv; char buf[BUF_LEN]; while(true) { int n = read(STDIN_FILENO, buf, BUF_LEN);
buf[n ] = '\0' // Igual que el cliente agregue el terminador de cadena manualmente
send_all(buf, n); // La entrada de información propia del servidor debe enviarse a todos los clientes}
close(sc); >
}int main(int argc, char *argv[])
{ struct sockaddr_in serv;
bzero(amp; serv, sizeof(serv)) ; p>
serv.sin_family = AF_INET;
serv.sin_addr.s_addr = htonl(INADDR_ANY);
serv.sin_port = htons(PORT); int ss = socket( AF_INET, SOCK_STREAM, 0); if(ss lt; 0) {
error("error de socket"); retorno 1;
} int err = bind(ss, (struct) sockaddr*)&serv, sizeof(serv)); if(err lt; 0) {
error("bind error");
}
err = escuchar(ss, 2);
if(err lt; 0) {
perror("error de escucha");
}
socks.clear(); Borrar mapa
socklen_t len = sizeof(struct sockaddr); while(true) { struct sockaddr_in *cli_addr = (struct sockaddr_in*)malloc(sizeof(struct sockaddr_in)); , (struct sockaddr*)cli_addr, amp; len); if(sc lt; 0) { free(cli_addr); continuar; if(socks.size() gt; = MAX_CONNECTION) { / / cuando Cuando el número máximo de conexiones esté a punto de excederse, deje que el cliente espere un momento
char buf[128] = "hay demasiadas conexiones, espere...\n";
enviar(sc, buf, strlen(buf) 1, 0
cerrar(sc); gratis(cli_addr);
} p>
socks [sc] = cli_addr; // Apunta al espacio sockaddr_in aplicado correspondiente
printf("client d connect me...\n", sc);
pthread_t td;
pthread_create(amp; td, NULL, proceso, (void*)amp; sc); // Abrir un hilo para interactuar con el cliente de aceptación} return
}
Makefile:
todos: servidor cliente
servidor: server.cpp
g -std=c 11 -o servidor servidor.cpp -lpthread
cliente: cliente.cpp
g -std=c 11 -o cliente cliente.cpp -lpthread
limpio:
rm -f *.o
Lo probé en mi máquina Ubuntu 14.04 de 64 bits y no hubo ningún problema. El cliente y el servidor pueden interactuar y salir normalmente, y pueden recibir. mensajes a través del servidor. Para los mensajes enviados por otros clientes, el uso de la CPU y la memoria es normal durante la operación y no se producirán errores extraños.
Por el momento, solo he escrito una interfaz de terminal. Trabajaré en la interfaz de usuario del cliente más adelante~
*********************. *** ************************************************* ************************************************* ***** *************
Hoy intenté usar PyQt4 para escribir una interfaz de cliente. Después de ajustarlo por un día, finalmente vi algo aquí. Esta es la imagen:
La interfaz de ejecución del cliente bajo la línea de comando (el archivo client.cpp arriba) es así:
El estado de ejecución del servidor es: p>
El código del cliente (pyqt_client.py) escrito en PyQt4 es:
#!/usr/bin/env python#-*-coding: utf-8 -*-from PyQt4 import QtGui , QtCoreimport sysimport socketimport threadclass Cliente(QtGui.QWidget):
BUF_LEN = 1024 def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.setWindowTitle(u'Cliente TCP')
self.resize(600, 500)
self.center()
diseño = QtGui.QGridLayout(self)
label_ip = QtGui.QLabel(u'IP del host remoto:')
layout.addWidget(label_ip, 0, 0, 1, 1)
self.txt_ip = QtGui.QLineEdit('127.0.0.1')
layout.addWidget(self.txt_ip, 0, 1, 1, 3)
label_port = QtGui .QLabel(u'port:')
layout.addWidget(label_port, 0, 4, 1, 1)
self.txt_port = QtGui.QLineEdit(' 9003')
layout.addWidget(self.txt_port, 0, 5, 1, 3)
self.isConnected = False
self.btn_connect = QtGui .QPushButton(u 'Conectar')
self.connect(self.btn_connect, QtCore.SIGNAL( 'clicked()'), self.myConnect)
layout.addWidget(self .btn_connect, 0, 8, 1, 2)
label_recvMessage = QtGui.QLabel(u'Contenido del mensaje:')
layout.addWidget(label_recvMessage, 1, 0, 1, 1)
p>self.btn_c
learRecvMessage = QtGui.QPushButton(u'↓ Borrar el cuadro de mensaje')
self.connect(self.btn_clearRecvMessage, QtCore.SIGNAL('clicked()'), self.myClearRecvMessage)
layout.addWidget(self.btn_clearRecvMessage, 1, 7, 1, 3)
self.txt_recvMessage = QtGui.QTextEdit()
self.txt_recvMessage.setReadOnly(True)
p>self.txt_recvMessage.setStyleSheet('color de fondo: amarillo')
layout.addWidget(self.txt_recvMessage, 2, 0, 1, 10)
lable_name = QtGui.QLabel(u'Nombre(ID):')
layout.addWidget(lable_name, 3, 0, 1, 1)
self.txt_name = QtGui .QLineEdit( )
layout.addWidget(self.txt_name, 3, 1, 1, 3)
self.isSendName = QtGui.QRadioButton(u'Enviar nombre') p>
self.isSendName.setChecked(False)
layout.addWidget(self.isSendName, 3, 4, 1, 1)
label_sendMessage = QtGui.QLabel(u ' cuadro de entrada :')
layout.addWidget(label_sendMessage, 4, 0, 1, 1)
self.txt_sendMessage = QtGui.QLineEdit()
self.txt_sendMessage .setStyleSheet("color de fondo: cian")
layout.addWidget(self.txt_sendMessage, 4, 1, 1, 7)
self.btn_send = QtGui. QPushButton(u 'Enviar')
self.connect(self.btn_send, QtCore.SIGNAL('clicked()'), self.mySend)
layout.addWidget(self. btn_send, 4, 8, 1, 2)
self.btn_clearSendMessage = QtGui.QPushButton(u' ↑ Borrar el cuadro de entrada')
self.connect(self.btn_clearSendMessage, QtCore .SIGNAL( 'clic()'), self.myClearSendMessage)
layout.addWidget
(self.btn_clearSendMessage, 5, 6, 1, 2)
self.btn_quit = QtGui.QPushButton(u'exit')
self.connect(self.btn_quit, QtCore.