서버에 접속한 user 중 한 명이 뭔가를 말하면 서버에 접속한 나머지 user들에게 전달해주는 채팅 프로그램을 만들것이다
user1이 접속하면 user1의 패킷만 받고 처리하고 있음.
user2가 들어오면 대기만 해야 함.
클라이언트는 미리 정해진 문자만 보낼 수밖에 없음.
채팅이 불가함.
받는 부분도 없음.
accept 부분을 쓰레드로 돌려서, user 전용 쓰레드와 전부를 위한 send 쓰레드를 만들어줄 것임.
이때 전용쓰레드는 read 만 가능하다.
송신 쓰레드 1(send)
수신 쓰레드 2(read)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <pthread.h>
#define CLNT_MAX 10
#define BUFFSIZE 200
int g_clnt_socks[CLNT_MAX];
int g_clnt_count = 0;
pthread_mutex_t g_mutex;
// 모든 클라이언트에게 메시지 전송
void send_all_clnt(char *msg, int str_len, int my_sock) {
pthread_mutex_lock(&g_mutex);
for (int i = 0; i < g_clnt_count; i++) {
if (g_clnt_socks[i] != my_sock) {
write(g_clnt_socks[i], msg, str_len);
}
}
pthread_mutex_unlock(&g_mutex);
}
만약, user1이 메시지를 보내면 서버에서 나머지 클라이언트들에게 메시지를 전달하는 역할이다.
// 클라이언트 연결 처리
void *clnt_connection(void *arg) {
int clnt_sock = *(int *)arg;
free(arg);
char msg[BUFFSIZE];
int str_len;
while (1) {
str_len = read(clnt_sock, msg, sizeof(msg));
if (str_len <= 0) { // Client disconnected or error
if (str_len == 0) {
printf("Client[%d] disconnected\n", clnt_sock);
} else {
perror("Read error");
}
break;
}
send_all_clnt(msg, str_len, clnt_sock);
printf("Message from client[%d]: %s", clnt_sock, msg);
}
// Remove client socket from the list
pthread_mutex_lock(&g_mutex);
for (int i = 0; i < g_clnt_count; i++) {
if (g_clnt_socks[i] == clnt_sock) {
for (int j = i; j < g_clnt_count - 1; j++) {
g_clnt_socks[j] = g_clnt_socks[j + 1];
}
g_clnt_count--;
break;
}
}
pthread_mutex_unlock(&g_mutex);
close(clnt_sock);
pthread_exit(NULL);
}
main()에서 클라이언트가 연결요청이 되어 클라이언트가 clnt_sock으로 들어오면 이 유저 전용 쓰레드로 들어오게 됨.
int main() {
int serv_sock, clnt_sock;
struct sockaddr_in serv_addr, clnt_addr;
socklen_t clnt_addr_size;
serv_sock = socket(PF_INET, SOCK_STREAM, 0);
if (serv_sock == -1) {
perror("Socket creation failed");
exit(EXIT_FAILURE);
}
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port = htons(7989);
if (bind(serv_sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1) {
perror("Bind failed");
close(serv_sock);
exit(EXIT_FAILURE);
}
if (listen(serv_sock, 5) == -1) {
perror("Listen failed");
close(serv_sock);
exit(EXIT_FAILURE);
}
pthread_mutex_init(&g_mutex, NULL);
printf("Server started on port 7989\n");
while (1) {
clnt_addr_size = sizeof(clnt_addr);
clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_addr, &clnt_addr_size);
if (clnt_sock == -1) {
perror("Accept failed");
continue;
}
assept()로 serv_sock에 클라이언트 연결 요청을 수락한다.
그 반환값을 clnt_sock에 넘겨준다.
pthread_mutex_lock(&g_mutex);
g_clnt_socks[g_clnt_count++] = clnt_sock;
pthread_mutex_unlock(&g_mutex);
int *arg = malloc(sizeof(int));
if (arg == NULL) {
perror("Malloc failed");
close(clnt_sock);
continue;
}
*arg = clnt_sock;
pthread_t t_thread;
if (pthread_create(&t_thread, NULL, clnt_connection, arg) != 0) {
perror("Thread creation failed");
free(arg);
close(clnt_sock);
continue;
}
pthread_detach(t_thread);
}
close(serv_sock);
pthread_mutex_destroy(&g_mutex);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <pthread.h>
#define BUFFSIZE 200
#define NAMESIZE 20
// 메시지 수신 쓰레드 함수
void *rcv(void *arg) {
int sock = *(int *)arg;
char buff[BUFFSIZE];
int len;
while (1) {
len = read(sock, buff, sizeof(buff));
if (len <= 0) { // Server closed connection or error
if (len == 0) {
printf("Server closed connection\n");
} else {
perror("Read error");
}
break;
}
printf("%s", buff); // Display received message
}
pthread_exit(NULL);
}
void *snd(void *arg) {
int sock = *(int *)arg;
char chat[BUFFSIZE];
char msg[BUFFSIZE + NAMESIZE];
char id[NAMESIZE];
printf("Enter your ID: ");
fgets(id, NAMESIZE, stdin);
id[strcspn(id, "\n")] = '\0'; // Remove newline character
while (1) {
printf("Enter message: ");
if (fgets(chat, sizeof(chat), stdin) == NULL) {
if (feof(stdin)) {
printf("EOF reached\n");
} else {
perror("Input error");
}
break;
}
chat[strcspn(chat, "\n")] = '\0'; // Remove newline character
snprintf(msg, sizeof(msg), "[%s]: %s\n", id, chat);
if (write(sock, msg, strlen(msg)) <= 0) {
perror("Write error");
break;
}
}
pthread_exit(NULL);
}
int main() {
int sock;
struct sockaddr_in serv_addr;
pthread_t rcv_thread, snd_thread;
sock = socket(PF_INET, SOCK_STREAM, 0);
if (sock == -1) {
perror("Socket creation failed");
return -1;
}
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
serv_addr.sin_port = htons(7989);
// 서버에 연결
if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1) {
perror("Connection failed");
close(sock);
return -1;
}
// 수신 및 송신 쓰레드 생성
if (pthread_create(&rcv_thread, NULL, rcv, (void *)&sock) != 0) {
perror("Receiver thread creation failed");
close(sock);
return -1;
}
if (pthread_create(&snd_thread, NULL, snd, (void *)&sock) != 0) {
perror("Sender thread creation failed");
pthread_cancel(rcv_thread);
close(sock);
return -1;
}
// 송신 및 수신 쓰레드 종료 대기
pthread_join(snd_thread, NULL);
pthread_cancel(rcv_thread);
close(sock);
return 0;
}