Home Papers Reports Projects Code Fragments Dissertations Presentations Posters Proposals Lectures given Course notes
2. UDP IP Sockets in C >>

1. TCP IP Sockets in C

Werner Van Belle1 - werner@yellowcouch.org, werner.van.belle@gmail.com

1- Programming Technology Lab (PROG) Department of Computer Science (DINF) Vrije Universiteit Brussel (VUB); Pleinlaan 2; 1050 Brussel; Belgium

Abstract :  These are course notes I made to teach distributed systems for the VUB. I used them in 1998 to 1999, 1999 to 2000, 2000 to 2001

Reference:  Werner Van Belle; TCP IP Sockets in C;
See also:
TCP/IP sockets in Java
Udp/Ip sockets


Info TCP/IP

Interprocesommunicatie is gebaseerd op het gebruik van socket paren. De communicatie zelf bestaat uit het versturen van berichten tussen twee aan elkaar geschakelde sockets. Dit kan zowel connectiegericht als connectieloos zijn. Berichten worden automatisch gequeued aan een zendende socket totdat het onderliggende netwerkprotocol in staat is het bericht te leveren en een acknowledge gestuurd heeft. Aan de ontvangende kant worden berichten gequeued totdat een proces ze van de socket haalt.

Elk proces kan een socket creeeren door een socket system call. Het eerste argument bevat het communicatiedomein (normaal internet, genoteerd door AF_INET). Het tweede argument bevat welk soort connectie gewenst is. Dit kan connectiegericht zijn (genoteerd door SOCK_STREAM) of connectieloos zijn (SOCK_DGRAM). De laatste parameter bepaalt welk onderliggend netwerkprotocol effectief gehanteerd wordt. Voor streaming gebruikt men normaal als protocol TCP/IP, voor datagrams gebruikt men UDP/IP. Als deze waarde op 0 staat wordt zelf een geschikt protocol gekozen. De socket call retourneerd een waarde die gebruikt kan worden als filedescriptor om data van te lezen en naartoe te sturen.

De socket system call creeert enkel een filedescriptor in geheugen. Eens dit gedaan is moeten we de socket verbinden aan een adres dat vanbuitenuit benadert kan worden of aan een adres waarmee we willen communiceren. De bind system call verbind de socket aan een lokaal adres waar data op ontvangen kan worden. De connect system call verbind de socket met een externe socket.

Een adres (sockaddr_in) wordt opgegeven in dit geval opgegeven door een poort en een IP nummer mee te geven. Onderstaande code initialiseert een socket en verbind naar een sendmail poort op een bepaalde machine. (Onder unix draaien normaal een aantal services die standaard een vaste poort gebruiken. Sendmail bijvoorbeelde gebruikt poort 25, poort 23 is om telnetconnecties te aanvaarden, poort 21 voor FTP requests en zo verder.)

#include <sys/types.h>
#include <sys/socket.h>
#include <err.h>
#include <errno.h>
#include <linux/in.h>
#include <netdb.h>

void main(void)
{
   int s;
   char b;
   struct hostent *h;
   struct sockaddr_in sa;
   /* socket adres maken */
   s=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
   if (s==-1) printf("socket: %s\n",strerror(errno));
   /* socket binden aan een deftig adres */
   memset(&sa,0,sizeof(struct sockaddr_in));
   sa.sin_port=htons(5);
   sa.sin_family=AF_INET;
   h=gethostbyname("4.4.9.1");
   memcpy(&sa.sin_addr,h->h_addr,h->h_length);
   if (-1==connect(s,(struct sockaddr_in*)&sa,sizeof(struct sockaddr_in)))
     printf("connect: %s\n",strerror(errno));
   /* lezen van data */
   while (1) 
     if (recv(s,&b,1,0)==1) putchar(b);
}

Als we zelf een server trachten te starten hebben we een socket nodig die een lokaal adres opeist. (een bepaalde poort voor zich reserveert). Dit wordt gedaan door de bind system call te gebruiken. Als dit gedaan is moet de application ''luisteren'' (listen) op deze poort en wachten op binnenkomende connecties. Eens een connectie binnengekomen is kan deze met accept van de connectie queue geplukt worden.

De accept call geeft een tweede socket weer die een specifieke stream met de client bevat. Onderstaande code initialiseert een service op poort 7060 die 'helo world' print als er naar getelnet wordt.

#include <sys/types.h>
#include <sys/socket.h>
#include <err.h>
#include <errno.h>
#include <linux/in.h>
#include <netdb.h>

void main(void)
{
   int serversocket, clientsocket;
   char b;
   struct hostent *h;
   struct sockaddr_in serveraddr,clientaddr;
   /* socket adres maken */
   serversocket=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
   if (serversocket==-1) printf("socket: %s\n",strerror(errno));
   /* server socket binden aan lokaal adres */
   memset(&serveraddr,0,sizeof(struct sockaddr_in));
   serveraddr.sin_port=htons(0);
   serveraddr.sin_family=AF_INET;
   serveraddr.sin_addr.s_addr=htonl(0);
   bind(serversocket,&serveraddr, sizeof(struct sockaddr_in));
   /* luisteren op server socket */
   if (-1==listen(serversocket,5))
     printf("listen: %s\n",strerror(errno));
   /* accepteren van de eerst binnenkomende connectie */
   if (-1==(clientsocket=accept(serversocket,&clientaddr,sizeof(struct sockaddr_in))))
     printf("listen: %s\n",strerror(errno));
   /* print 'hello world' or something alike */
   send(clientsocket,"bored",6,0);
   /* boeltje dicht smijten */
   close(clientsocket);
   shutdown(clientsocket,2);
   close(serversocket);
   shutdown(serversocket,2);
}

Oef1: Minesweeper Design

Schrijf een multi player minesweeper. De bedoeling is dat meerder spelers tegelijk op een slagveld kunnen ontmijnen. De spelers hun score gaat omhoog door een juiste vakje te ontmijnen. Spelers vallen dood door op een mijn te tikken. Als het spel voorbij is worden de scores van al de spelers afgeprint.

Design deze toepassing. Hou rekening hoe users connecteren, disconnecteren, waar het speelveld in geheugen gehouden wordt. Welk protocol gehanteerd wordt. Wie welke errorchecks doet. Zet duidelijk voorhand op papier welke berichten verstaan worden en hoe de server van state naar state springt.

Oef2: Minesweeper Server

Schrijf de minesweeper server. Je kan deze testen door zelf te telnetten naar je minesweeperserver.

Oplossing

/* 
 *  client -> server
 *    byte 0: 'H'  (hitposition)
 *    byte 1-4: <x>
 *    byte 5-8: <y>
 *  server -> client
 *    byte 0: 'S'  (showposition)
 *    byte 1-4: <x>
 *    byte 5-8: <y>
 *    byte 9: <value>
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <linux/in.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <signal.h>
#include <sys/utsname.h>
#include <unistd.h>
#include <termios.h>
#include <mntent.h>
#include <getopt.h>
#include <errno.h>
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <curses.h>

#define XSIZ  0
#define YSIZ  0
#define MAXPLAYERS 0
static int playfield[XSIZ][YSIZ];
static int stayrunning = 1;
static int serversocket;

typedef struct sclient {
   int socket;
   int score;
   int status; /* 0 = niet actiev; 1 = actiev; 2 = duud */
} client;

static client clients[MAXPLAYERS];

int score=0;
/* -1: niet gemarkeerd als mijn, effectief mijn (onzichtbaar)
 * -2: niet gemarkeerd als mijn, leeg (onzichtbaar)
 * -3: gemarkeerd als mijn, effectief mijn (onzichtbaar)
 * -4: gemarkeerd als mijn, leeg (onzichtbaar)
 * 0: leeg vak, (zichtbaar)
 * 1 -> 8: x buren (zichtbaar)
 * 9: mijn (zichtbaar)
 */

int mine(int w)
{
   return w==9 || w==-1 || w==-3;
}

int countmines(int x, int y)
{
   int answer = 0;
   assert(x>=0);
   assert(y>=0);
   assert(x<XSIZ);
   assert(y<YSIZ);
   if (mine(playfield[x-1][y-1])) answer++;
   if (mine(playfield[x-1][y])) answer++;
   if (mine(playfield[x-1][y+1])) answer++;
   if (mine(playfield[x][y-1])) answer++;
   if (mine(playfield[x][y+1])) answer++;
   if (mine(playfield[x+1][y-1])) answer++;
   if (mine(playfield[x+1][y])) answer++;
   if (mine(playfield[x+1][y+1])) answer++;
   return answer;
}

void sendpion(int id, int x, int y)
{
   char buf[6];
   buf[0]='S';
   *((int*)(buf+1))=x; 
   *((int*)(buf+5))=y; 
   *((signed char*)(buf+9))=playfield[x][y];
   send(clients[id].socket,buf,0,0);
}

void sendupdate(int x, int y)
{
   int i;
   for(i=0;i<MAXPLAYERS;i++)
     {
	if (clients[i].status==1 && clients[i].socket>0)
	  {
	     sendpion(i,x,y);
	  }
     }
}

void sendfield(int id)
{
   int x, y;
   printf("SENDING FIELD /.. \n"); 
   for(x=0;x<XSIZ;x++)
     for(y=0;y<YSIZ;y++)
       sendpion(id,x,y);
}

int hitposition(int x, int y)
/* nieuwe waarde van het vakje */
{
   if (x<=0) return 0;
   if (x>=XSIZ-1) return 0;
   if (y<=0) return 0;
   if (y>=YSIZ-1) return 0;
   if (playfield[x][y]>=0) return playfield[x][y];
   if (playfield[x][y]==-1 || playfield[x][y]==-3)  
     {
	playfield[x][y]=9;
	sendupdate(x,y);
     }
   if (playfield[x][y]==-2 || playfield[x][y]==-4)
     {
	playfield[x][y]=countmines(x,y);
	sendupdate(x,y);
     }
   if (playfield[x][y]>0) return playfield[x][y];
   hitposition(x-1,y);
   hitposition(x+1,y);
   hitposition(x,y-1);
   hitposition(x,y+1);
   hitposition(x-1,y-1);
   hitposition(x+1,y+1);
   hitposition(x+1,y-1);
   hitposition(x-1,y+1);
   return playfield[x][y];
}

void fillplayfield(int aantalmijnen, int aantaltrys)
{
   int x,y;
   srandom(time());
   assert(aantalmijnen>=0);
   for(x=0;x<XSIZ;x++)
     for(y=0;y<YSIZ;y++)
     playfield[x][y]=-2;
   while(--aantalmijnen>=0)
     {
	x=random()/(RAND_MAX/(XSIZ-2));
	y=random()/(RAND_MAX/(YSIZ-2));
	playfield[x+1][y+1]=-1;
     }
   while(--aantaltrys>=0)
     {
	x=random()/(RAND_MAX/(XSIZ-2));
	y=random()/(RAND_MAX/(YSIZ-2));
	hitposition(x+1,y+1);
     }
}

void showplayfield()
{
   int x,y,i;
   printf("\x1b\x5b\x48\x1b\x5b\x4a\n");
   for(y=1;y<YSIZ-1;y++)
     {
	for(x=1;x<XSIZ-1;x++)
	  {
	     if (playfield[x][y]==-1) printf("\x1b[1m\x1b[2;41m [] ");
	     if (playfield[x][y]==-2) printf("\x1b[1m\x1b[2;41m [] ");
	     if (playfield[x][y]==-3) printf("\x1b[1m\x1b[1;41m MM ");
	     if (playfield[x][y]==-4) printf("\x1b[1m\x1b[1;41m MM ");
	     if (playfield[x][y]==0) printf("\x1b[1m\x1b[7;41m    ");
	     if (playfield[x][y]==1) printf("\x1b[1m\x1b[7;41m 1  ");
	     if (playfield[x][y]==2) printf("\x1b[1m\x1b[7;41m 2  ");
	     if (playfield[x][y]==3) printf("\x1b[1m\x1b[7;41m 3  ");
	     if (playfield[x][y]==4) printf("\x1b[1m\x1b[7;41m 4  ");
	     if (playfield[x][y]==5) printf("\x1b[1m\x1b[7;41m 5  ");
	     if (playfield[x][y]==6) printf("\x1b[1m\x1b[7;41m 6  ");
	     if (playfield[x][y]==7) printf("\x1b[1m\x1b[7;41m 7  ");
	     if (playfield[x][y]==8) printf("\x1b[1m\x1b[7;41m 8  ");
	     if (playfield[x][y]==9) printf("\x1b[1m\x1b[1;41m XX \x1b[00m");
	  }
	printf("\n");
     }
   for(i=0;i<MAXPLAYERS;i++)
     {
	printf("%d\t",clients[i].score);
     }
   printf("\n");
}

void hitspot(int id, int x, int y)
{
   int newvalue;
   /* geen punten bij als reeds geweten is wat er ligt
    * wel pech als er reeds een mijn ligt */
   assert(x>0);
   assert(y>0);
   assert(x<XSIZ-1);
   assert(y<YSIZ-1);
   if (playfield[x][y]==9)
     clients[id].status=2;
/*   if (playfield[x][y]>=0) return;*/
   newvalue=hitposition(x,y);
   if (newvalue==9) 
     clients[id].status=2;
   else clients[id].score++;
   showplayfield();
}

void incomingconnections()
{
   int clientsocket, i;
   struct sockaddr_in clientaddr;
   if (-1==(clientsocket=accept(serversocket,(struct sockaddr*)&clientaddr,sizeof(struct sockaddr_in))))
     {
/*	printf("accept: %s\n",strerror(errno));*/
	return;
     }
   fcntl(clientsocket,F_SETFL,O_NONBLOCK);
   printf(". . . incoming . .  .\n");
   for(i=0;i<MAXPLAYERS;i++)
     {
	if (clients[i].status==0 && clients[i].socket==-1)
	  {
	     clients[i].socket=clientsocket;
	     sendfield(i);
	     clients[i].status=1;
	     return;
	  }
     }
   assert(0);
}

void incomingmessages()
{
   int i, b, count, x, y;
   char buf[6];
   for(i=0;i<MAXPLAYERS;i++)
     {
	if (clients[i].status==1  && clients[i].socket>0)
	  {
	     if (recv(clients[i].socket,&b,1,0)==1)
	       {
		  assert(b=='H');
		  count=recv(clients[i].socket,buf,8,0);
		  assert(count==8);
		  x=*(int*)buf;
		  y=*(int*)(buf+4);
		  hitspot(i,x,y);
	       }
	  }
     }
}

void initserver()
{
   struct hostent *h;
   struct sockaddr_in serveraddr;
   int i;
   /* client table leeg maken */
   for(i=0;i<MAXPLAYERS;i++)
     {
	clients[i].socket=-1;
	clients[i].score=0;
	clients[i].status=0;
     }
   /* socket maken */
   serversocket=socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
   if (serversocket==-1) printf("socket: %s\n",strerror(errno));
   /* server socket binden aan lokaal adres */
   memset(&serveraddr,0,sizeof(struct sockaddr_in));
   serveraddr.sin_port=htons(0);
   serveraddr.sin_family=AF_INET;
   serveraddr.sin_addr.s_addr=htonl(0);
   bind(serversocket,(struct sockaddr*)&serveraddr,sizeof(struct sockaddr_in));
   /* start listening + asynchroon */
   fcntl(serversocket,F_SETFL,O_NONBLOCK);
   if (-1==listen(serversocket,MAXPLAYERS))
     printf("listen: %s\n",strerror(errno));
}

void main(void)
{
   fillplayfield(0,0);
   initserver();
   showplayfield();
   while(1)
     {
	incomingconnections();
	incomingmessages();
     }
}

Oef3: Minesweeper Client

Schrijf de minesweeper client. Gebruik als user interface een textmode en als invoerdevice een commandline waarop coordinaten ingetikt kunnen worden.

Oplossing

#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <linux/in.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <signal.h>
#include <sys/utsname.h>
#include <unistd.h>
#include <termios.h>
#include <mntent.h>
#include <getopt.h>
#include <errno.h>
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <curses.h>
#define XSIZ  0
#define YSIZ  0
static int playfield[XSIZ][YSIZ];
static int playerX=0;
static int playerY=0;
static int stayrunning = 1;
static int serversocket;

/* -1: niet gemarkeerd als mijn, effectief mijn (onzichtbaar)
 * -2: niet gemarkeerd als mijn, leeg (onzichtbaar)
 * -3: gemarkeerd als mijn, effectief mijn (onzichtbaar)
 * -4: gemarkeerd als mijn, leeg (onzichtbaar)
 * 0: leeg vak, (zichtbaar)
 * 1 -> 8: x buren (zichtbaar)
 * 9: mijn (zichtbaar)
 */
void fillplayfield()
{
   int x,y;
   for(x=0;x<XSIZ;x++)
     for(y=0;y<YSIZ;y++)
       playfield[x][y]=-2;
}

void showplayfield()
{
   int x,y;
   printf("\x1b\x5b\x48\x1b\x5b\x4a\n");
   for(y=1;y<YSIZ-1;y++)
     {
	for(x=1;x<XSIZ-1;x++)
	  {
	     if (x==playerX && y==playerY)
	       {
		  if (playfield[x][y]==-1) printf("\x1b[1m\x1b[3;46m#[]#\x1b[00m");
		  if (playfield[x][y]==-2) printf("\x1b[1m\x1b[3;46m#[]#\x1b[00m");
		  if (playfield[x][y]==-3) printf("\x1b[1m\x1b[3;46m#MM#\x1b[00m");
		  if (playfield[x][y]==-4) printf("\x1b[1m\x1b[3;46m#MM#\x1b[00m");
		  if (playfield[x][y]==0) printf("\x1b[1m\x1b[3;46m####\x1b[00m");
		  if (playfield[x][y]==1) printf("\x1b[1m\x1b[3;46m#1##\x1b[00m");
		  if (playfield[x][y]==2) printf("\x1b[1m\x1b[3;46m#2##\x1b[00m");
		  if (playfield[x][y]==3) printf("\x1b[1m\x1b[3;46m#3##\x1b[00m");
		  if (playfield[x][y]==4) printf("\x1b[1m\x1b[3;46m#4##\x1b[00m");
		  if (playfield[x][y]==5) printf("\x1b[1m\x1b[3;46m#5##\x1b[00m");
		  if (playfield[x][y]==6) printf("\x1b[1m\x1b[3;46m#6##\x1b[00m");
		  if (playfield[x][y]==7) printf("\x1b[1m\x1b[3;46m#7##\x1b[00m");
		  if (playfield[x][y]==8) printf("\x1b[1m\x1b[3;46m#8##\x1b[00m");
		  if (playfield[x][y]==9) printf("\x1b[1m\x1b[3;46m#XX#\x1b[00m");
	       }
	     else
	       {
		  if (playfield[x][y]==-1) printf("\x1b[1m\x1b[2;44m [] ");
		  if (playfield[x][y]==-2) printf("\x1b[1m\x1b[2;44m [] ");
		  if (playfield[x][y]==-3) printf("\x1b[1m\x1b[1;44m MM ");
		  if (playfield[x][y]==-4) printf("\x1b[1m\x1b[1;44m MM ");
		  if (playfield[x][y]==0) printf("\x1b[1m\x1b[7;44m    ");
		  if (playfield[x][y]==1) printf("\x1b[1m\x1b[7;44m 1  ");
		  if (playfield[x][y]==2) printf("\x1b[1m\x1b[7;44m 2  ");
		  if (playfield[x][y]==3) printf("\x1b[1m\x1b[7;44m 3  ");
		  if (playfield[x][y]==4) printf("\x1b[1m\x1b[7;44m 4  ");
		  if (playfield[x][y]==5) printf("\x1b[1m\x1b[7;44m 5  ");
		  if (playfield[x][y]==6) printf("\x1b[1m\x1b[7;44m 6  ");
		  if (playfield[x][y]==7) printf("\x1b[1m\x1b[7;44m 7  ");
		  if (playfield[x][y]==8) printf("\x1b[1m\x1b[7;44m 8  ");
		  if (playfield[x][y]==9) printf("\x1b[1m\x1b[1;44m XX \x1b[00m");
	       }
	  }
	printf("\n");
     }
}

void markspot(int x, int y)
{
   assert(x>0);
   assert(y>0);
   assert(x<XSIZ-1);
   assert(y<YSIZ-1);
   if (playfield[x][y]>=0) return;
   if (playfield[x][y]==-1) playfield[x][y]=-3;
   else if (playfield[x][y]==-2) playfield[x][y]=-4;
   else if (playfield[x][y]==-3) playfield[x][y]=-1;
   else if (playfield[x][y]==-4) playfield[x][y]=-2;
   showplayfield();
}

void hitspot(int x, int y)
{
   char buf[6];
   buf[0]='H';
   *((int*)(buf+1))=x;
   *((int*)(buf+5))=y;
   send(serversocket,buf,9,0);
}

void playermoved()
{
   if (playerX<1) playerX=1;
   if (playerY<1) playerY=1;
   if (playerX>XSIZ-2) playerX=XSIZ-2;
   if (playerY>YSIZ-2) playerY=YSIZ-2;
   showplayfield();
}

/* A: moveleft
 * S: moveright
 * W: up
 * Z: down
 * M: markeer als mijn
 * X: exit
 * spatie: hit the spot
 */
void rl_ttyset (int Reset)
{
  static struct termios old;
  struct termios new;
   if (Reset == 0)
     {(void) tcgetattr (0, &old);
      new = old;
      new.c_lflag &= ~(ECHO | ICANON);
      new.c_iflag &= ~(ISTRIP | INPCK);
      (void) tcsetattr (0, TCSANOW, &new);}
   else (void) tcsetattr (0, TCSANOW, &old);
   fcntl(0,F_SETFL,O_NONBLOCK);
   fcntl(1,F_SETFL,O_NONBLOCK);
   fcntl(2,F_SETFL,O_NONBLOCK);
}

void eventloop()
{
   int changed=0;
   int c,nr,x,y,v;
   char b, buf[6];
   while(stayrunning)
     {
	/* network check */
	changed=0;
	while (recv(serversocket,&b,1,0)==1)
	  {
	     /*printf("%d, %c\n",b,b);*/
	     assert(b=='S');
	     recv(serversocket,buf,9,0);
	     x=*(int*)buf;
	     y=*(int*)(buf+4);
	     v=*(signed char*)(buf+8);
	     playfield[x][y]=v;
	     changed=1;
	  }
	if (changed) showplayfield();
	/* keyboard check */
	c=0;
	nr=read(0,&c,1);
	if (nr==0) continue;
	changed=1;
	if (c=='q')
	  {  playerX--;
	     playermoved();}
	if (c=='s')
	  {  playerX++;
	     playermoved();}
	if (c=='z')
	  {  playerY--;
	     playermoved();}
	if (c=='w')
	  {  playerY++;
	     playermoved();}
	if (c=='x')
	  stayrunning=0;
	if (c=='m')
	  markspot(playerX,playerY);
	     if (c==' ')
	  hitspot(playerX,playerY);
     }
}

void initclient()
{
   struct hostent *h;
   struct sockaddr_in serveraddr;
   int i;
   /* socket maken */
   serversocket=socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
   if (serversocket==-1) printf("socket: %s\n",strerror(errno));
   /* server socket binden aan lokaal adres */
   memset(&serveraddr,0,sizeof(struct sockaddr_in));
   serveraddr.sin_port=htons(0);
   serveraddr.sin_family=AF_INET;
   h=gethostbyname("7.0.0.1");
   memcpy(&serveraddr.sin_addr,h->h_addr,h->h_length);
   /* connect */
   if (-1==connect(serversocket,(struct sockaddr_in*)&serveraddr,sizeof(struct sockaddr_in)))
     printf("connect: %s\n",strerror(errno));
   /* asynchroon zetten */
   fcntl(serversocket,F_SETFL,O_NONBLOCK);
}

void main(void)
{
   rl_ttyset(0);
   fillplayfield();
   initclient();
   eventloop();
   rl_ttyset(1);
}

http://werner.yellowcouch.org/
werner@yellowcouch.org