server.c
- multithreaded server
server_single.c
- singlethreaded server
client.c
- client
This is pre-project tutorial for socket programming
If you are familiar to socket programming, then jump to Let’s Practice Part
- With socket, two different processes can communicate each other
- Socket is nothing but a
file
- You can just imagine that two different processes have files (socket) and the they read received data from socket and write to socket for sending data to network
- So, socket has file descriptor, which is just an integer to identify opened file
There are two commonly used socket types Stream Sockets
and Datagram Sockets
. Stream sockets uses TCP for data transmission, and Datagram sockets uses UDP.
- Create a socket with the
socket()
system call - Connect socket to the address of the server using the
connect()
system call - Send and receive data. There are a number of ways to do this, but the simplest way is to use the
read()
andwrite()
system calls
- Create a socket with the
socket()
system call - Bind the socket to an address (IP + port) using the
bind()
system call. - Listen for connections with the
listen()
system call - Accept a connection with the
accept()
system call. This call typically blocks the connection until a client connects with the server - Send and receive data using the
read()
andwrite()
system calls
Interaction between server and client
You will use socket functions, and most of the socket functions use socket address structures.
sockaddr
: generic socket address structure
struct sockaddr {
// represents an address family, most of cases AF_INET)
unsigned short sa_family;
// 14 bytes of protocol specific address, for the internet
// family, port number IP address (sockaddr_in) is used
char sa_data[14];
}
sockaddr_in
: one type of sockaddr, it represents port number IP address
struct sockaddr_in {
short int sin_family; // AF_INET
unsigned short int sin_port; // 16-bit port number
struct in_addr sin_addr; // 32-bit IP address
unsigned char sin_zero[8];
}
in_addr
: structure used in abote sockaddr_in
struct in_addr {
unsigned long s_addr;
}
hostent
: contains information related to host
struct hostent {
char *h_name; // e.g. unist.ac.kr
char **h_aliases; // list of host name alias
int h_addrtype; // AF_INET
int h_length; // length of ip address
char **h_addr_list; // points to structure in_addr
#define h_addr h_addr_list[0]
};
All computers doesn’t store bytes in same order. → Two different ways
- Little Endian : low-order byte is stored on the starting addresses
- Bit Endian : high-order byte is stored on the starting address
→ To make machines with different byte order communicate each other, Internet protocol specify a canonical byte order convention for data transmitted over the network. This is called Network Byte Order.
sin_port
sin_addr
of sockaddr_in
should be set with this Network Byte Order.
htons() : Host to Network Short
htonl() : Host to Network Long
ntohl() : Network to Host Long
ntohs() : Network to Host Short
These functions manipulate IP addresses between ASCII strings and network byte ordered binary values.
- int inet_aton(const char *strptr, struct in_addr *addrptr)
#include <arpa/inet.h>
int retval;
struct in_addr addrptr
memset(&addrptr, '\0', sizeof(addrptr));
retval = inet_aton("68.178.157.132", &addrptr);
- in_addr_t inet_addr(const char *strptr)
#include <arpa/inet.h>
struct sockaddr_in dest;
memset(&dest, '\0', sizeof(dest));
dest.sin_addr.s_addr = inet_addr("68.178.157.132");
char *inet_ntoa(struct in_addr inaddr)
#include <arpa/inet.h>
char *ip;
ip = inet_ntoa(dest.sin_addr);
printf("IP Address is : %s\n", ip);
(you can use bold scripted parameter for the first use)
socket
#include <sys/types.h>
#include <sys/socket.h>
int socket (int family, int type, int protocol);
- family : AF_INET, AF_INET6, AF_LOCAL, AF_ROUTE, AF_KEY
- type : SOCK_STREAM (TCP), SOCK_DGRAM (UDP), SOCK_SEQPACKET, SOCK_RAW
- protocol : IPPROTO_TCP, IPPROTO_UDP, IPPROTO_SCTP, (0 : system default)
→ This function returns socket descriptor, so you can use it for another functions
connect
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);
- sockfd : socket descriptor returned by the socket function
- serv_addr : sockaddr that contains destination IP address and port
- addrlen : set it to sizeof(struct sockaddr)
bind
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, struct sockaddr *my_addr,int addrlen);
- my_addr : sockaddr that contains local IP address and port
listen
#include <sys/types.h>
#include <sys/socket.h>
int listen(int sockfd,int backlog);
- converts unconnected socket to passive socket (kernel should accept incoming connection request directed to this socket)
- backlog : maximum number of connections the kernel should queue for this socket
accept
#include <sys/types.h>
#include <sys/socket.h>
int accept (int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen)
- returns the next completed connection from the front of the completed connection queue
- cliaddr : sockaddr struct that contains client IP address and port
- addrlen : set it to sizeof (struct sockaddr)
send
int send(int sockfd, const void *msg, int len, int flags);
recv
int recv(int sockfd, void *buf, int len, unsigned int flags);
- buf : buffer to read the information into
- len : It is the maximum length of the buffer
- flags : set it to 0
- for UDP connection
int sendto(int sockfd, const void *msg, int len, unsigned int flags, const struct sockaddr *to, int tolen);
int recvfrom(int sockfd, void *buf, int len, unsigned int flags struct sockaddr *from, int *fromlen);
sendto
andrecvfrom
functions are used instead ofsend
recv
close
int close( int sockfd );
- close communication
fork
- creates new process which is exact copy of current process
- current process is parent, and copied process is child
#include <sys/types.h>
#include <unistd.h>
int fork(void);
- fork returns 0 when it is child process, and returns child process id when it is parent process. If it is failed, returns -1
bzero
- place nbytes of null byte to string s
void bzero(void *s, int nbyte);
bcmp
- compare nbytes of byte string s1 and byte string s2
int bcmp(const void *s1, const void *s2, int nbyte);
returns 0 if identical, 1 otherwise.
bcopy
- copy nbytes of byte string s1 to byte string s2
void bcopy(const void *s1, void *s2, int nbyte);
memset
- allocate memory and return pointer which points to the newly allocated memory
void *memset(void *s, int c, int nbyte);
- s : source to be set
- c : character to set on nbyte places
- nbyte : number of bytes
- You have to implement connecting server and client, and your server and client should quit when the client sends “quit” you can compile code with command line in the directory of project :
> make
- run server and client on server at the same time
- with two different terminal windows
> ./client
> ./server
- Sample Output
client
server
- There can be multiple clients which tries to connect to server simultaneously
- Current echo server doesn’t accept new connection if it is already accepted
- You have to change echo server. Using fork() function, each connection should be run concurrently.
- Hint (pseudo code of multi user server) :
listen()
while (1) {
newsockfd = accept();
pid = fork();
if (pid == 0) { // client process
close(sockfd);
// do some process - read and write
exit(0);
} else { // parent process
close(newsockfd);
}
}
- Test : you can just open three terminal windows, run one server and two clients
- test order :
> run one server and two clients
> client1 sends “hello1”
> client2 sends “hello2”
> client2 sends “world2”
> client1 sends “world1”
> client1 sends “quit” and client2 sends “quit”
Output :
client1
client2
multi user server