수색…
TCP 동시 에코 서버
이 예에서는 지정된 포트에서 수신 대기하고 새 연결을 처리 할 수있는 간단한 에코 서버를 만듭니다.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <time.h>
/**
Connection handler - this will be executed in
the new process, after forking, and it will read
all the data from the socket, while available and
to echo it on the local terminal.
Params:
sd = socket to the client
*/
#define BUF_SIZE (1024)
int echo_client(int sd)
{
int result = 0;
char buf[BUF_SIZE + 1] = {0};
ssize_t n_read;
while (0 < (n_read = read(sd, buf, BUF_SIZE)))
{
buf[n_read] = '\0';
printf("%s\n", buf);
}
if (0 > n_read)
{
perror("read() failed");
result = -1;
}
else
{
fprintf(stderr, "The other side orderly shut down the connection.\n");
}
close(sd);
return result;
}
int main(void)
{
// Create a listening socket
int listening_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (listening_socket == -1)
{
perror("socket() failed");
return EXIT_FAILURE;
}
// Bind it to port 15000.
unsigned short listening_port = 15000;
struct sockaddr_in addr = {0};
addr.sin_family = AF_INET;
addr.sin_port = htons(listening_port);
socklen_t sock_len = sizeof(addr);
if (0 > (bind(listening_socket, (const struct sockaddr*) &addr, sock_len)))
{
perror("bind() failed");
return EXIT_FAILURE;
}
// Start listening
if (0 > listen(listening_socket, 0))
{
perror("listen() failed");
return EXIT_FAILURE;
}
// Accept new connections, fork the new process for handling
// and handle the connection in the new process, while the parent
// is waiting for another connection to arrive.
int accepted_socket = 0;
while (0 < (accepted_socket =
accept(listening_socket, (struct sockaddr*) &addr, &sock_len)))
{
pid_t pid_child = fork();
if (0 > pid_child)
{
perror("fork() failed");
return EXIT_FAILURE;
}
else if (0 == pid_child)
{
// inside the forked child here
close(listening_socket); // The child does not need this any more.
echo_client(accepted_socket);
return EXIT_SUCCESS;
}
// Inside parent process, since file descriptors are reference
// counted, we need to close the client socket
close(accepted_socket);
}
if (0 > accepted_socket)
{
perror("accept() failed");
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
서버 쪽에서 TCP keepalive 사용
이것은 클라이언트 - 서버 예제입니다. 프로세스는 하위 프로세스의 부모 프로세스와 서버에서 클라이언트를 fork하고 실행합니다.
- 클라이언트는 서버에 연결하고 서버가 종료 될 때까지 대기합니다.
- 서버는 클라이언트로부터의 연결을 허용하고, 연결 유지를 활성화하고, 모든 신호를 대기합니다.
Keepalive는 socket(7)
및 tcp(7)
매뉴얼 페이지에 설명 된 다음 옵션을 사용하여 구성됩니다.
-
SO_KEEPALIVE
- 연결 유지 메시지 전송을 가능하게합니다. -
TCP_KEEPIDLE
- TCP가 keepalive 프로브 전송을 시작하기 전에 연결을 유휴 상태로 유지해야하는 시간 (초) -
TCP_KEEPINTVL
- 개별 keepalive 프로브 간의 시간 (초) -
TCP_KEEPCNT
- TCP가 연결을 끊기 전에 보내야하는 최대 keepalive 프로브 수
소스 코드:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/signal.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#define check(expr) if (!(expr)) { perror(#expr); kill(0, SIGTERM); }
void enable_keepalive(int sock) {
int yes = 1;
check(setsockopt(
sock, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof(int)) != -1);
int idle = 1;
check(setsockopt(
sock, IPPROTO_TCP, TCP_KEEPIDLE, &idle, sizeof(int)) != -1);
int interval = 1;
check(setsockopt(
sock, IPPROTO_TCP, TCP_KEEPINTVL, &interval, sizeof(int)) != -1);
int maxpkt = 10;
check(setsockopt(
sock, IPPROTO_TCP, TCP_KEEPCNT, &maxpkt, sizeof(int)) != -1);
}
int main(int argc, char** argv) {
check(argc == 2);
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(12345);
check(inet_pton(AF_INET, argv[1], &addr.sin_addr) != -1);
int server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
check(server != -1);
int yes = 1;
check(setsockopt(server, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) != -1);
check(bind(server, (struct sockaddr*)&addr, sizeof(addr)) != -1);
check(listen(server, 1) != -1);
if (fork() == 0) {
int client = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
check(client != -1);
check(connect(client, (struct sockaddr*)&addr, sizeof(addr)) != -1);
printf("connected\n");
pause();
}
else {
int client = accept(server, NULL, NULL);
check(client != -1);
enable_keepalive(client);
printf("accepted\n");
wait(NULL);
}
return 0;
}
Keepalive 패킷은 tcpdump
사용하여 모니터 할 수 있습니다.
사용 예 :
$ ./a.out 127.0.0.1 &
[1] 14010
connected
accepted
$ tcpdump -n -c4 -ilo port 12345
dropped privs to tcpdump
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on lo, link-type EN10MB (Ethernet), capture size 262144 bytes
18:00:35.173892 IP 127.0.0.1.12345 > 127.0.0.1.60998: Flags [.], ack 510307430, win 342, options [nop,nop,TS val 389745775 ecr 389745675], length 0
18:00:35.173903 IP 127.0.0.1.60998 > 127.0.0.1.12345: Flags [.], ack 1, win 342, options [nop,nop,TS val 389745775 ecr 389745075], length 0
18:00:36.173886 IP 127.0.0.1.12345 > 127.0.0.1.60998: Flags [.], ack 1, win 342, options [nop,nop,TS val 389745875 ecr 389745775], length 0
18:00:36.173898 IP 127.0.0.1.60998 > 127.0.0.1.12345: Flags [.], ack 1, win 342, options [nop,nop,TS val 389745875 ecr 389745075], length 0
4 packets captured
8 packets received by filter
0 packets dropped by kernel
TCP 주간 반복 서버
이것은 TCP 주간 반복 서버로서 가능한 한 간단하게 유지됩니다.
#include <sys/types.h> /* predefined types */
#include <unistd.h> /* unix standard library */
#include <arpa/inet.h> /* IP addresses conversion utilities */
#include <netinet/in.h> /* sockaddr_in structure definition */
#include <sys/socket.h> /* berkley socket library */
#include <stdio.h> /* standard I/O library */
#include <string.h> /* include to have memset */
#include <stdlib.h> /* include to have exit */
#include <time.h> /* time manipulation primitives */
#define MAXLINE 80
#define BACKLOG 10
int main(int argc, char *argv[])
{
int list_fd, conn_fd;
struct sockaddr_in serv_add;
char buffer[MAXLINE];
time_t timeval;
/* socket creation third parameter should be IPPROTO_TCP but 0 is an
* accepted value */
list_fd = socket(AF_INET, SOCK_STREAM, 0);
/* address initialization */
memset(&serv_add, 0, sizeof(serv_add)); /* init the server address */
serv_add.sin_family = AF_INET; /* address type is IPV4 */
serv_add.sin_port = htons(13); /* daytime port is 13 */
serv_add.sin_addr.s_addr = htonl(INADDR_ANY); /* connect from anywhere */
/* bind socket */
bind(list_fd, (struct sockaddr *)&serv_add, sizeof(serv_add));
/* listen on socket */
listen(list_fd, BACKLOG);
while (1)
{
/* accept connection */
conn_fd = accept(list_fd, (struct sockaddr *) NULL, NULL);
timeval = time(NULL);
snprintf(buffer, sizeof(buffer), "%.24s\r\n", ctime(&timeval));
write(conn_fd, buffer, strlen(buffer)); /* write daytime to client */
close(conn_fd);
}
/* normal exit */
close(list_fd);
exit(0);
}
TCP 주간 클라이언트
이것은 TCP 주간 클라이언트로서 가능한 한 단순하게 유지됩니다.
#include <unistd.h> /* unix standard library */
#include <arpa/inet.h> /* IP addresses manipulation utilities */
#include <netinet/in.h> /* sockaddr_in structure definition */
#include <sys/socket.h> /* berkley socket library */
#include <stdio.h> /* standard I/O library */
#include <string.h> /* include to have memset*/
#include <stdlib.h> /* include to have exit*/
#define MAXLINE 1024
int main(int argc, char *argv[])
{
int sock_fd;
int nread;
struct sockaddr_in serv_add;
char buffer[MAXLINE];
/* socket creation third parameter should be IPPROTO_TCP but 0 is an
* accepted value*/
sock_fd = socket(AF_INET, SOCK_STREAM, 0);
/* address initialization */
memset(&serv_add, 0, sizeof(serv_add)); /* init the server address */
serv_add.sin_family = AF_INET; /* address type is IPV4 */
serv_add.sin_port = htons(13); /* daytime post is 13 */
/* using inet_pton to build address */
inet_pton(AF_INET, argv[1], &serv_add.sin_addr);
/* connect to the server */
connect(sock_fd, (struct sockaddr *)&serv_add, sizeof(serv_add));
/* read daytime from server */
while ((nread = read(sock_fd, buffer, MAXLINE)) > 0)
{
buffer[nread] = 0;
if (fputs(buffer, stdout) == EOF)
{
perror("fputs error"); /* write daytime on stdout */
return -1;
}
}
close(sock_fd);
exit(0);
}
소켓 기본 사항
POSIX API에는 TCP, UDP, UNIX 및 RAW (선택 사항)의 네 가지 유형의 소켓이 있습니다. Unix 도메인 소켓은 스트림 소켓이나 데이터 그램 소켓과 같은 역할을합니다.
일부 엔드 포인트 유형 :
-
struct sockaddr
- 유니버설 엔드 포인트 유형. 일반적으로 다른 구체적인 끝점 유형은 posix 호출에서만이 유형으로 변환됩니다. -
struct sockaddr_in
- IPv4 엔드 포인트
struct sockaddr_in {
sa_family_t sin_family;
in_port_t sin_port; /* Port number. */
struct in_addr sin_addr; /* Internet address. */
};
struct in_addr {
in_addr_t s_addr;
};
-
struct sockaddr_in6
- IPv6 끝점
struct sockaddr_in6 {
sa_family_t sin6_family;
in_port_t sin6_port; /* Transport layer port # */
uint32_t sin6_flowinfo; /* IPv6 flow information */
struct in6_addr sin6_addr; /* IPv6 address */
uint32_t sin6_scope_id; /* IPv6 scope-id */
};
-
struct sockaddr_un
.
struct sockaddr_un {
sa_family_t sun_family; /* AF_UNIX */
char sun_path[108]; /* pathname */
};
전체 프로그램
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <unistd.h>
#define DESIRED_ADDRESS "127.0.0.1"
#define DESIRED_PORT 3500
#define BUFSIZE 512
int main()
{
// ADDRESS PART
// MAIN PART
close(sock);
return EXIT_SUCCESS;
}
IPv4 끝점 만들기
struct sockaddr_in addr = {0};
addr.sin_family = AF_INET;
addr.sin_port = htons(DESIRED_PORT); /*converts short to
short with network byte order*/
addr.sin_addr.s_addr = inet_addr(DESIRED_ADDRESS);
TCP 서버 스 니펫
int sock = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sock == -1) {
perror("Socket creation error");
return EXIT_FAILURE;
}
if (bind(sock, (struct sockaddr*) &addr, sizeof(addr)) == -1) {
perror("Bind error");
close(sock);
return EXIT_FAILURE;
}
if (listen(sock, 1/*length of connections queue*/) == -1) {
perror("Listen error");
close(sock);
return EXIT_FAILURE;
}
socklen_t socklen = sizeof addr;
int client_sock = accept(sock, &addr, &socklen); /* 2nd and 3rd argument may be NULL. */
if (client_sock == -1) {
perror("Accept error");
close(sock);
return EXIT_FAILURE;
}
printf("Client with IP %s connected\n", inet_ntoa(addr.sin_addr));
char buf[BUFSIZE];
if (send(sock, "hello", 5, 0) == -1) {
perror("Send error");
close(client_sock);
close(sock);
return EXIT_FAILURE;
}
ssize_t readden = recv(sock, buf, BUFSIZE, 0);
if (readden < 0) {
perror("Receive error");
close(client_sock);
close(sock);
return EXIT_FAILURE;
}
else if (readden == 0) {
fprintf(stderr, "Client orderly shut down the connection.\n");
}
else {readden > 0) {
if (readden < BUFSIZE)
{
fprintf(stderr, "Received less bytes (%zd) then requested (%d).\n",
readden, BUFSIZE);
}
write (STDOUT_FILENO, buf, readden);
}
TCP 클라이언트 스 니펫
int sock = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sock == -1) {
perror("Socket creation error");
return EXIT_FAILURE;
}
if (connect(sock, (struct sockaddr*) &addr, sizeof(addr)) == -1) {
perror("Connection error");
close(sock);
return EXIT_FAILURE;
}
char buf[BUFSIZE];
if (send(sock, "hello", 5, 0); /*write may be also used*/ == -1) {
perror("Send error");
close(client_sock);
close(sock);
return EXIT_FAILURE;
}
ssize_t readden = recv(sock, buf, BUFSIZE, 0); /*read may be also used*/
if (readden < 0) {
perror("Receive error");
close(client_sock);
close(sock);
return EXIT_FAILURE;
}
else if (readden == 0)
{
fprintf(stderr, "Client orderly shut down the connection.\n");
}
else /* if (readden > 0) */ {
if (readden < BUFSIZE)
{
fprintf(stderr, "Received less bytes (%zd) then requested (%d).\n",
readden, BUFSIZE);
}
write (STDOUT_FILENO, buf, readden);
}
UDP 서버 코드
int sock = socket (AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sock == -1) {
perror("Socket creation error");
return EXIT_FAILURE;
}
if (bind(sock, (struct sockaddr*) &addr, sizeof(addr)) == -1) {
perror("Bind error");
close(sock);
return EXIT_FAILURE;
}
char buf[BUFSIZE];
ssize_t readden = recvfrom(sock, buf, BUFSIZE, 0, &addr, sizeof(addr));
if (readden > 0) {
printf("Client with IP %s sent datagram\n", inet_ntoa(addr.sin_addr));
write (STDOUT_FILENO, buf, readden);
}
sendto(sock, "hello", 5, 0, &addr, sizeof(addr));
블로킹 소켓에서 연결 수락
네트워크 연결 ( "서버"역할)을 받아들이려는 AC 프로그램은 먼저 "INADDR_ANY"주소에 바인드 된 소켓을 만들고이를 listen
해야합니다. 그런 다음 클라이언트가 연결할 때까지 서버 소켓에서 accept
를 호출하여 차단할 수 있습니다.
//Create the server socket
int servsock = socket(AF_INET, SOCK_STREAM, 0);
if(servsock < 0) perror("Failed to create a socket");
int enable = 1;
setsockopt(servsock, SOL_SOCKET, SO_REUSEADDR, (char*)&enable, sizeof(int));
//Bind to "any" address with a specific port to listen on that port
int port = 12345;
sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = INADDR_ANY;
serv_addr.sin_port = htons(port);
if(bind(servsock, (sockaddr*)&serv_addr, sizeof(serv_addr)) < 0)
perror("Error binding to socket");
listen(servsock, 5);
//Accept a client
struct sockaddr_storage client_addr_info;
socklen_t len = sizeof client_addr_info;
int clientsock = accept(servsock, (struct sockaddr*)&client_addr_info, &len);
//Now you can call read, write, etc. on the client socket
accept
전달 된 sockaddr_storage
구조체를 사용하여 연결된 클라이언트에 대한 정보를 검색 할 수 있습니다. 예를 들어 클라이언트의 IP 주소를 확인하는 방법은 다음과 같습니다.
char client_ip_str[INET6_ADDRSTRLEN + 1];
if(client_addr_info.ss_family == AF_INET) {
// Client has an IPv4 address
struct sockaddr_in *s = (struct sockaddr_in *)&client_addr_info;
inet_ntop(AF_INET, &s->sin_addr, client_ip_str, sizeof(client_ip_str));
} else { // AF_INET6
// Client has an IPv6 address
struct sockaddr_in6 *s = (struct sockaddr_in6 *)&client_addr_info;
inet_ntop(AF_INET6, &s->sin6_addr, client_ip_str, sizeof(client_ip_str));
}
원격 호스트에 연결
서버 이름을 문자열, char* servername
및 포트 번호, int port
로 지정하면 다음 코드는 해당 서버에 연결된 소켓을 만들고 엽니 다. 서버의 "이름"은 "www.stackoverflow.com"과 같은 DNS 이름이거나 표준 표기법의 IP 주소 (예 : "192.30.253.113") 일 수 있습니다. 어느 쪽의 입력 형식도 gethostbyname
( POSIX.1-2008로부터 삭제되었다 )에 유효합니다.
char * server = "www.example.com";
int sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock < 0)
perror("Failed to create a socket");
hostent *server = gethostbyname(servername);
if (server == NULL)
perror("Host lookup failed");
char server_ip_str[server->h_length];
inet_ntop(AF_INET, server->h_addr, server_ip_str, server->h_length);
sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(port);
memcpy(&serv_addr.sin_addr.s_addr, server->h_addr, server->h_length);
if (connect(sock, (sockaddr*)&serv_addr, sizeof(serv_addr)) < 0)
perror("Failed to connect");
// Now you can call read, write, etc. on the socket.
close(sock);
블로킹 소켓 읽기 및 쓰기
소켓이 "블로킹"모드 일 때에도 소켓의 read
및 write
작업이 반드시 읽거나 쓸 수있는 모든 데이터를 읽고 쓰는 것은 아닙니다. 전체 버퍼를 소켓에 쓰거나 소켓에서 알려진 양의 데이터를 읽으려면 루프에서 호출해야합니다.
/*
* Writes all bytes from buffer into sock. Returns true on success, false on failure.
*/
bool write_to_socket(int sock, const char* buffer, size_t size) {
size_t total_bytes = 0;
while(total_bytes < size) {
ssize_t bytes_written = write(sock, buffer + total_bytes, size - total_bytes);
if(bytes_written >= 0) {
total_bytes += bytes_written;
} else if(bytes_written == -1 && errno != EINTR) {
return false;
}
}
return true;
}
/*
* Reads size bytes from sock into buffer. Returns true on success; false if
* the socket returns EOF before size bytes can be read, or if there is an
* error while reading.
*/
bool read_from_socket(int sock, char* buffer, size_t size) {
size_t total_bytes = 0;
while(total_bytes < size) {
ssize_t new_bytes = read(sock, buffer + total_bytes, size - total_bytes);
if(new_bytes > 0) {
total_bytes += new_bytes;
} else if(new_bytes == 0 || (new_bytes == -1 && errno != EINTR)) {
return false;
}
}
return true;
}