Socket Programming - Learning C by Example (2015)

Learning C by Example (2015)

10. Socket Programming

This chapter explains how to get started with Socket programming using C.

10.1 Getting Local Hostname

We usually know our hostname using command hostname. For instance, you can type the following command.

$ hostname

We also can get our hostname by program. We can use gethostname() function. It is defined as below.

#include <unistd.h>

int gethostname(char *name, size_t len);

For illustration, we create a simple app to read local hostname. Create a file, called gethostname.c, and write this code.

#include <stdio.h>

#include <sys/unistd.h>

int main(void)

{

char hostname[128];

gethostname(hostname, sizeof(hostname));

printf("My local hostname: %s\n", hostname);

return 0;

}

Save this code and try to compile and run.

The following is a sample output.

ch10-1

10.2 Creating and Connecting

In this section we explore socket programming. For illustration we build client-server app. In this scenario, we just connect and then close.

To create communication, we can use socket() function. It can be defined as below.

#include <sys/types.h>

#include <sys/socket.h>

int socket(int domain, int type, int protocol);

domain is defined as below.

Name Purpose

--------------------------------------------------

AF_UNIX, AF_LOCAL Local communication

AF_INET IPv4 Internet protocols

AF_INET6 IPv6 Internet protocols

AF_IPX IPX - Novell protocols

AF_NETLINK Kernel user interface device

AF_X25 ITU-T X.25 / ISO-8208 protocol

AF_AX25 Amateur radio AX.25 protocol

AF_ATMPVC Access to raw ATM PVCs

AF_APPLETALK Appletalk

AF_PACKET Low level packet interface

type is defined as below.

SOCK_STREAM Provides sequenced, reliable, two-way, connection-

based byte streams. An out-of-band data transmission

mechanism may be supported.

SOCK_DGRAM Supports datagrams (connectionless, unreliable

messages of a fixed maximum length).

SOCK_SEQPACKET Provides a sequenced, reliable, two-way connection-

based data transmission path for datagrams of fixed

maximum length; a consumer is required to read an

entire packet with each input system call.

SOCK_RAW Provides raw network protocol access.

SOCK_RDM Provides a reliable datagram layer that does not

guarantee ordering.

SOCK_PACKET Obsolete and should not be used in new programs;

We will focus on socket type: SOCK_STREAM and SOCK_DGRAM.

10.2.1 Server

To build server, you can follow steps, shown in Figure below.

ch10-6

bind(), listen(), accept() and close() function can be defines as follows.

#include <sys/types.h>

#include <sys/socket.h>

#include <unistd.h>

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

int listen(int sockfd, int backlog);

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

int close(int fd);

We usually struct sockaddr_in to pass parameter addr. It is defined as follows.

struct sockaddr_in {

sa_family_t sin_family; /* address family: AF_INET */

in_port_t sin_port; /* port in network byte order */

struct in_addr sin_addr; /* internet address */

};

struct in_addr {

uint32_t s_addr; /* address in network byte order */

};

For implementation, we build server app by creating a file, called simple_server.c, and write this code.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

#include <stdio.h>

#include <sys/types.h>

#include <sys/socket.h>

#include <netinet/in.h>

#include <string.h>

#include <errno.h>

int main(void)

{

int sockfd, newsockfd, portno, clilen;

struct sockaddr_in serv_addr, cli_addr;

portno = 8056; // server port

errno = 0;

sockfd = socket(AF_INET, SOCK_STREAM, 0);

if (sockfd < 0)

{

printf("\n socket() failed with error [%s]\n",strerror(errno));

return -1;

}

bzero((char *) &serv_addr, sizeof(serv_addr));

serv_addr.sin_family = AF_INET;

serv_addr.sin_addr.s_addr = INADDR_ANY;

serv_addr.sin_port = htons(portno);

if (bind(sockfd, (struct sockaddr *) &serv_addr,

sizeof(serv_addr)) < 0)

{

printf("\n bind() failed with error [%s]\n",strerror(errno));

return -1;

}

printf("socket has created and binded\n");

printf("listening incoming socket client....\n");

listen(sockfd,5);

clilen = sizeof(cli_addr);

newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen);

if (newsockfd < 0)

{

printf("\n accept() failed with error [%s]\n",strerror(errno));

return -1;

}

printf("a client was connected\n");

// close socket

close(newsockfd);

close(sockfd);

return 0;

}

You can compile this program.

$ gcc -o simple_server simple_server.c

10.2.2 Client

To connect the server, we can use connect() function. This function can be defined as below.

#include <sys/types.h>

#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr *addr,

socklen_t addrlen);

Server information is passed to addr parameter.

For client implementation, we create a file, called simple_client.c, and write this code.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

#include <stdio.h>

#include <sys/types.h>

#include <sys/socket.h>

#include <netinet/in.h>

#include <netdb.h>

#include <string.h>

#include <errno.h>

int main(void)

{

int sockfd, portno;

struct sockaddr_in serv_addr;

struct hostent *server;

// server hostname. You can change it

char server_hostname[] = "ubuntu15";

portno = 8056; // server port

errno = 0;

sockfd = socket(AF_INET, SOCK_STREAM, 0);

if (sockfd < 0)

{

printf("\n socket() failed with error [%s]\n",strerror(errno));

return -1;

}

printf("socket has created\n");

server = gethostbyname(server_hostname);

if (server == NULL)

{

printf("\n gethostbyname() failed with error [%s]\n",strerror(errno));

return -1;

}

bzero((char *) &serv_addr, sizeof(serv_addr));

serv_addr.sin_family = AF_INET;

bcopy((char *)server->h_addr,

(char *)&serv_addr.sin_addr.s_addr,

server->h_length);

serv_addr.sin_port = htons(portno);

printf("connecting to server....\n");

if (connect(sockfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr)) < 0)

{

printf("\n connect() failed with error [%s]\n",strerror(errno));

return -1;

}

printf("connected to server\n");

// close socket

close(sockfd);

return 0;

}

Save all code. Try to compile.

$ gcc -o simple_client simple_client.c

10.2.3 Testing

Now we can test our server and client app. Firstly we run server app. The following is a sample output.

ch10-2

Then you run client app.

ch10-3

After client connected to server, server and client close connection.

10.3 Data Transfer

We can send and receive data via socket using read() and write functions. These functions are defined as below.

#include <unistd.h>

ssize_t read(int fd, void *buf, size_t count);

ssize_t write(int fd, const void *buf, size_t count);

These functions are general I/O functions. We can use specific I/O for socket operations using send() and recv() functions. They can implemented as follows.

#include <sys/types.h>

#include <sys/socket.h>

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

In this section we build client-server like section 10.2 but we try to transfer data using write() and read() functions.

10.3.1 Server

After client connected, we read data from client using read() function. Then we send message to client using write() function. After that, we close connection.

For implementation, we create a file, called data_server.c, and write this code.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

#include <stdio.h>

#include <sys/types.h>

#include <sys/socket.h>

#include <netinet/in.h>

#include <string.h>

#include <errno.h>

int main(void)

{

int sockfd, newsockfd, portno, clilen, sz;

char buffer[256];

struct sockaddr_in serv_addr, cli_addr;

portno = 8059; // server port

errno = 0;

sockfd = socket(AF_INET, SOCK_STREAM, 0);

if (sockfd < 0)

{

printf("\n socket() failed with error [%s]\n",strerror(errno));

return -1;

}

bzero((char *) &serv_addr, sizeof(serv_addr));

serv_addr.sin_family = AF_INET;

serv_addr.sin_addr.s_addr = INADDR_ANY;

serv_addr.sin_port = htons(portno);

if (bind(sockfd, (struct sockaddr *) &serv_addr,

sizeof(serv_addr)) < 0)

{

printf("\n bind() failed with error [%s]\n",strerror(errno));

return -1;

}

printf("socket has created and binded\n");

printf("listening incoming socket client....\n");

listen(sockfd,5);

clilen = sizeof(cli_addr);

newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen);

if (newsockfd < 0)

{

printf("\n accept() failed with error [%s]\n",strerror(errno));

return -1;

}

printf("a client was connected\n");

// read data

sz = read(newsockfd,buffer,255);

if (sz < 0)

{

printf("\n read() failed with error [%s]\n",strerror(errno));

return -1;

}

printf("Received message: %s\n",buffer);

// write data

sz = write(newsockfd,"this is message from server",30);

if (sz < 0)

{

printf("\n write() failed with error [%s]\n",strerror(errno));

return -1;

}

// close socket

close(newsockfd);

close(sockfd);

return 0;

}

This code is similar to code on section 10.2.1 but we continue to read and send data.

Save this code and compile it.

$ gcc -o data_server data_server.c

10.3.2 Client

On client side, we send message after connected. Then we read message from server.

Let's start to create a file, called data_client.c, and write this code.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

#include <stdio.h>

#include <sys/types.h>

#include <sys/socket.h>

#include <netinet/in.h>

#include <netdb.h>

#include <string.h>

#include <errno.h>

int main(void)

{

int sockfd, portno, sz;

struct sockaddr_in serv_addr;

struct hostent *server;

char buffer[256];

// server hostname. You can change it

char server_hostname[] = "ubuntu15";

portno = 8059; // server port

errno = 0;

sockfd = socket(AF_INET, SOCK_STREAM, 0);

if (sockfd < 0)

{

printf("\n socket() failed with error [%s]\n",strerror(errno));

return -1;

}

printf("socket has created\n");

server = gethostbyname(server_hostname);

if (server == NULL)

{

printf("\n gethostbyname() failed with error [%s]\n",strerror(errno));

return -1;

}

bzero((char *) &serv_addr, sizeof(serv_addr));

serv_addr.sin_family = AF_INET;

bcopy((char *)server->h_addr,

(char *)&serv_addr.sin_addr.s_addr,

server->h_length);

serv_addr.sin_port = htons(portno);

printf("connecting to server....\n");

if (connect(sockfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr)) < 0)

{

printf("\n connect() failed with error [%s]\n",strerror(errno));

return -1;

}

printf("connected to server\n");

// write data

strcpy(buffer,"this is message from client");

sz = write(sockfd,buffer,strlen(buffer));

if (sz < 0)

{

printf("\n write() failed with error [%s]\n",strerror(errno));

return -1;

}

// read data

bzero(buffer,256);

sz = read(sockfd,buffer,255);

if (sz < 0)

{

printf("\n read() failed with error [%s]\n",strerror(errno));

return -1;

}

printf("Received message: %s\n",buffer);

// close socket

close(sockfd);

return 0;

}

Save this code and compile it.

$ gcc -o data_client data_client.c

10.3.3 Testing

We are ready to test our client-server app. Firstly we run server app.

The following is a sample output for server app.

ch10-4

Then we run client app. You can see the output, shown in Figure below.

ch10-5