Linux sockets: Difference between revisions
Jump to navigation
Jump to search
Use
Program crashes on
(→C API) |
|||
Line 14: | Line 14: | ||
:* [http://man7.org/linux/man-pages/man7/socket.7.html socket (7)] |
:* [http://man7.org/linux/man-pages/man7/socket.7.html socket (7)] |
||
== API == |
|||
=== C === |
|||
;read(2) - Read from a file descriptor |
;read(2) - Read from a file descriptor |
||
<source lang="c"> |
<source lang="c"> |
||
Line 47: | Line 48: | ||
</source> |
</source> |
||
* The main difference with <code>write</code> is the presence of <code>flags</code>, which among other can configured to prevent the generation of SIGPIPE signal. |
* The main difference with <code>write</code> is the presence of <code>flags</code>, which among other can configured to prevent the generation of SIGPIPE signal. |
||
== Examples == |
|||
=== C server === |
|||
The following example comes from https://www.binarytides.com/socket-programming-c-linux-tutorial/. |
|||
To use this code: |
|||
<source lang="bash"> |
|||
gcc server.c -lpthread -o server |
|||
./server |
|||
# In another window: |
|||
telnet localhost 8888 |
|||
</source> |
|||
Changes: |
|||
* Set socket option <code>SO_REUSEADDR</code> to allow reconnect to same port while previous server instance is closing the connection (socket in TIME_WAIT state). |
|||
* Use <code>send</code> instead of <code>write</code> to avoid SIGPIPE signal if clients closed the socket. |
|||
* Don't use <code>strlen</code> on received message because there is no guarantee of terminating zero. Instead use <code>read_size</code>. |
|||
* Test error code of <code>send</code> / <code>write</code>. |
|||
<source lang="c"> |
|||
// Server - Example code from https://www.binarytides.com/socket-programming-c-linux-tutorial/ |
|||
// |
|||
// Example of use: |
|||
// |
|||
// * In one window: |
|||
// | |
|||
// | ./server |
|||
// | |
|||
// * In another window: |
|||
// | |
|||
// | telnet localhost 8888 |
|||
// | |
|||
// | Then type anything. |
|||
#include <stdio.h> |
|||
#include <string.h> // strlen |
|||
#include <stdlib.h> // strlen |
|||
#include <sys/socket.h> |
|||
#include <arpa/inet.h> // inet_addr |
|||
#include <unistd.h> // write |
|||
#include <errno.h> |
|||
#include <pthread.h> // for threading , link with lpthread |
|||
void * connection_handler(void *); |
|||
int main(int argc, char *argv[]) |
|||
{ |
|||
int sockfd, new_socket, c, *new_sock; |
|||
struct sockaddr_in server, client; |
|||
char * message; |
|||
// Create socket |
|||
sockfd = socket(AF_INET, SOCK_STREAM, 0); |
|||
if ( sockfd == -1 ) { |
|||
printf("Could not create socket"); |
|||
return 1; |
|||
} |
|||
// Allow address/port reuse in a case server is killed (socket in TIME_WAIT state) |
|||
if ( setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &(int){1 }, sizeof(int)) < 0 ) { |
|||
printf("setsockopt(SO_REUSEADDR) failed"); |
|||
return 1; |
|||
} |
|||
// Prepare the sockaddr_in structure |
|||
server.sin_family = AF_INET; |
|||
server.sin_addr.s_addr = INADDR_ANY; |
|||
server.sin_port = htons(8888); |
|||
// Bind |
|||
if ( bind(sockfd, (struct sockaddr *)&server, sizeof(server)) < 0 ) { |
|||
printf("bind failed: %s (errno %d)", strerror(errno), errno); |
|||
return 1; |
|||
} |
|||
puts("bind done"); |
|||
// Listen |
|||
listen(sockfd, 3); |
|||
// Accept and incoming connection |
|||
puts("Waiting for incoming connections..."); |
|||
c = sizeof(struct sockaddr_in); |
|||
while ((new_socket = accept(sockfd, (struct sockaddr *)&client, (socklen_t *)&c))) { |
|||
puts("Connection accepted"); |
|||
// Reply to the client |
|||
message = "Hello Client , I have received your connection. And now I will assign a handler for you\n"; |
|||
if ( send(new_socket, message, strlen(message), MSG_NOSIGNAL) < 0 ) { |
|||
perror("send failed"); |
|||
} |
|||
pthread_t sniffer_thread; |
|||
new_sock = malloc(1); |
|||
*new_sock = new_socket; |
|||
if ( pthread_create(&sniffer_thread, NULL, connection_handler, (void *)new_sock) < 0 ) { |
|||
perror("could not create thread"); |
|||
return 1; |
|||
} |
|||
puts("Handler assigned"); |
|||
} |
|||
// TODO: Join the running threads, so that we don't terminate before them |
|||
// for( int i=0; ...; ... ) pthread_join( sniffer_thread[i] , NULL); |
|||
if ( new_socket < 0 ) { |
|||
perror("accept failed"); |
|||
return 1; |
|||
} |
|||
return 0; |
|||
} |
|||
/* |
|||
* This will handle connection for each client |
|||
* */ |
|||
void * connection_handler(void *sockfd) |
|||
{ |
|||
// Get the socket descriptor |
|||
int sock = *(int *)sockfd; |
|||
int read_size; |
|||
char *message, client_message[2048]; |
|||
// Send some messages to the client |
|||
message = "Greetings! I am your connection handler\n"; |
|||
if ( send(sock, message, strlen(message), MSG_NOSIGNAL) < 0 ) { |
|||
perror("send failed"); |
|||
} |
|||
message = "Now type something and i shall repeat what you type \n"; |
|||
if ( send(sock, message, strlen(message), MSG_NOSIGNAL) < 0 ) { |
|||
perror("send failed"); |
|||
} |
|||
// Receive a message from client |
|||
while ((read_size = recv(sock, client_message, sizeof(client_message), 0)) > 0 ) { |
|||
// Output received message (assuming zero-terminated) |
|||
write(STDOUT_FILENO, client_message, read_size); |
|||
// Send the message back to client |
|||
send(sock, client_message, read_size, MSG_NOSIGNAL); |
|||
} |
|||
if ( read_size == 0 ) { |
|||
puts("Client disconnected"); |
|||
fflush(stdout); |
|||
} else if ( read_size == -1 ) { |
|||
perror("recv failed"); |
|||
} |
|||
// Free the socket pointer |
|||
free(sockfd); |
|||
return 0; |
|||
} |
|||
</source> |
|||
== Troubleshooting == |
== Troubleshooting == |
Revision as of 07:42, 28 September 2018
Tutorials
- A thoough step-by-step example of client and socket code.
- Simple example of client and server code using Linux sockets.
References
- A very detailed answer about the differences between
SO_REUSEADDR
andSO_REUSEPORT
.
- A course on internet technologies, including Domain Naming (DNS...), the IP protocol, the TCP protocol, ...
- Manpages:
API
C
- read(2) - Read from a file descriptor
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
- Mostly equivalent to
recv
except there is no specification for flags (e.g. to change blocking/non-blocking behaviour).
- recv(2), recvfrom(2), recmmsg(2) - Receive a message from a socket
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
- write(2) - Write to a file descriptor
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
- The main difference with
send
is that whenwrote
returnsEPIPE
, the writing process will also receive a SIGPIPE signal, terminating it unless it catches it or ignore it.
- send(2), sendto(2), sendmsg(2) - Send a message on a socket
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
- The main difference with
write
is the presence offlags
, which among other can configured to prevent the generation of SIGPIPE signal.
Examples
C server
The following example comes from https://www.binarytides.com/socket-programming-c-linux-tutorial/.
To use this code:
gcc server.c -lpthread -o server
./server
# In another window:
telnet localhost 8888
Changes:
- Set socket option
SO_REUSEADDR
to allow reconnect to same port while previous server instance is closing the connection (socket in TIME_WAIT state). - Use
send
instead ofwrite
to avoid SIGPIPE signal if clients closed the socket. - Don't use
strlen
on received message because there is no guarantee of terminating zero. Instead useread_size
. - Test error code of
send
/write
.
// Server - Example code from https://www.binarytides.com/socket-programming-c-linux-tutorial/
//
// Example of use:
//
// * In one window:
// |
// | ./server
// |
// * In another window:
// |
// | telnet localhost 8888
// |
// | Then type anything.
#include <stdio.h>
#include <string.h> // strlen
#include <stdlib.h> // strlen
#include <sys/socket.h>
#include <arpa/inet.h> // inet_addr
#include <unistd.h> // write
#include <errno.h>
#include <pthread.h> // for threading , link with lpthread
void * connection_handler(void *);
int main(int argc, char *argv[])
{
int sockfd, new_socket, c, *new_sock;
struct sockaddr_in server, client;
char * message;
// Create socket
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if ( sockfd == -1 ) {
printf("Could not create socket");
return 1;
}
// Allow address/port reuse in a case server is killed (socket in TIME_WAIT state)
if ( setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &(int){1 }, sizeof(int)) < 0 ) {
printf("setsockopt(SO_REUSEADDR) failed");
return 1;
}
// Prepare the sockaddr_in structure
server.sin_family = AF_INET;
server.sin_addr.s_addr = INADDR_ANY;
server.sin_port = htons(8888);
// Bind
if ( bind(sockfd, (struct sockaddr *)&server, sizeof(server)) < 0 ) {
printf("bind failed: %s (errno %d)", strerror(errno), errno);
return 1;
}
puts("bind done");
// Listen
listen(sockfd, 3);
// Accept and incoming connection
puts("Waiting for incoming connections...");
c = sizeof(struct sockaddr_in);
while ((new_socket = accept(sockfd, (struct sockaddr *)&client, (socklen_t *)&c))) {
puts("Connection accepted");
// Reply to the client
message = "Hello Client , I have received your connection. And now I will assign a handler for you\n";
if ( send(new_socket, message, strlen(message), MSG_NOSIGNAL) < 0 ) {
perror("send failed");
}
pthread_t sniffer_thread;
new_sock = malloc(1);
*new_sock = new_socket;
if ( pthread_create(&sniffer_thread, NULL, connection_handler, (void *)new_sock) < 0 ) {
perror("could not create thread");
return 1;
}
puts("Handler assigned");
}
// TODO: Join the running threads, so that we don't terminate before them
// for( int i=0; ...; ... ) pthread_join( sniffer_thread[i] , NULL);
if ( new_socket < 0 ) {
perror("accept failed");
return 1;
}
return 0;
}
/*
* This will handle connection for each client
* */
void * connection_handler(void *sockfd)
{
// Get the socket descriptor
int sock = *(int *)sockfd;
int read_size;
char *message, client_message[2048];
// Send some messages to the client
message = "Greetings! I am your connection handler\n";
if ( send(sock, message, strlen(message), MSG_NOSIGNAL) < 0 ) {
perror("send failed");
}
message = "Now type something and i shall repeat what you type \n";
if ( send(sock, message, strlen(message), MSG_NOSIGNAL) < 0 ) {
perror("send failed");
}
// Receive a message from client
while ((read_size = recv(sock, client_message, sizeof(client_message), 0)) > 0 ) {
// Output received message (assuming zero-terminated)
write(STDOUT_FILENO, client_message, read_size);
// Send the message back to client
send(sock, client_message, read_size, MSG_NOSIGNAL);
}
if ( read_size == 0 ) {
puts("Client disconnected");
fflush(stdout);
} else if ( read_size == -1 ) {
perror("recv failed");
}
// Free the socket pointer
free(sockfd);
return 0;
}
Troubleshooting
Use errno / perror / strerror to get error
if( bind(socket_desc,(struct sockaddr *)&server , sizeof(server)) < 0)
{
printf("bind failed: %s (errno %d)", strerror(errno), errno);
return 1;
}
Or there is also the perror
function:
if( bind(socket_desc,(struct sockaddr *)&server , sizeof(server)) < 0)
{
perror("bind failed");
return 1;
}
Use netstat
to get socket status
./server
# bind done
# Waiting for incoming connections...
# Connection accepted
# Handler assigned
# ^C
./server
# bind failed: Address already in use (errno 98)
netstat -a | grep 8888
# tcp 0 0 zavcxl0005:58888 165.225.76.32:http ESTABLISHED
# tcp 0 0 zavcxl0005:48888 10.75.126.1:https ESTABLISHED
# tcp 0 0 localhost.localdom:8888 localhost.localdo:50310 TIME_WAIT
# ... So port is still in use
bind failed: Address already in use (errno 98)
Call to bind
fails with error
bind failed: Address already in use (errno 98)
On a server, this is probably due to an old instance of the server that tries to bind to the same port and address [1].
As a fix, try to bind with option SO_REUSEADDR
and/or SO_REUSEPORT
. From SO:
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &(int){ 1 }, sizeof(int)) < 0)
error("setsockopt(SO_REUSEADDR) failed");
For more information, see socket(7) and ip(7) manpages.
Program crashes on write
with exit code -13
Exit code -13 means the program died because of SIGPIPE signal, meaning it tried to write to a closed socket.
Solutions:
- Ignore SIGPIPE in the program [2]:
signal (SIGPIPE, SIG_IGN);
- Use
send
instead ofwrite
. Send returns -1 and seterrno
toEPIPE
rather than generating a fatal SIGPIPE likewrite
does [3]:
n = send(newsockfd, data.c_str(), data.length()+1, MSG_NOSIGNAL);