Movatterモバイル変換


[0]ホーム

URL:


Jump to content
WikibooksThe Free Textbook Project
Search

C Programming/Networking in UNIX

From Wikibooks, open books for an open world
<C Programming
Thelatest reviewed version waschecked on5 July 2025. There is1 pending change awaiting review.
Preprocessor directives and macrosC Programming
Networking in UNIX
X macros and serialization

Network programming under UNIX is relatively simple in C.

This guide assumes you already have a good general idea about C, UNIX and networks.


A simple client

[edit |edit source]

To start with, we'll look at one of the simplest things you can do: initialize a stream connection and receive a message from a remote server.

#include<stdio.h>#include<stdlib.h>#include<string.h>#include<unistd.h>#include<arpa/inet.h>#include<sys/types.h>#include<netinet/in.h>#include<sys/socket.h>#define MAXRCVLEN 500#define PORTNUM 2300intmain(intargc,char*argv[]){charbuffer[MAXRCVLEN+1];/* +1 so we can add null terminator */intlen,mysocket;structsockaddr_indest;mysocket=socket(AF_INET,SOCK_STREAM,0);memset(&dest,0,sizeof(dest));/* zero the struct */dest.sin_family=AF_INET;dest.sin_addr.s_addr=htonl(INADDR_LOOPBACK);/* set destination IP number - localhost, 127.0.0.1*/dest.sin_port=htons(PORTNUM);/* set destination port number */connect(mysocket,(structsockaddr*)&dest,sizeof(structsockaddr_in));len=recv(mysocket,buffer,MAXRCVLEN,0);/* We have to null terminate the received data ourselves */buffer[len]='\0';printf("Received %s (%d bytes).\n",buffer,len);close(mysocket);returnEXIT_SUCCESS;}

This is the very bare bones of a client; in practice, we would check every function that we call for failure, however, error checking has been left out for clarity.

As you can see, the code mainly revolves arounddest which is a struct of typesockaddr_in. This struct stores information about the machine we want to connect to.

mysocket=socket(AF_INET,SOCK_STREAM,0);

Thesocket() function tells our OS that we want a file descriptor for a socket which we can use for a network stream connection; what the parameters mean is mostly irrelevant for now.

memset(&dest,0,sizeof(dest));/* zero the struct */dest.sin_family=AF_INET;dest.sin_addr.s_addr=inet_addr("127.0.0.1");/* set destination IP number */dest.sin_port=htons(PORTNUM);/* set destination port number */

Now we get on to the interesting part:

The first line usesmemset() to zero the struct.

The second line sets the address family. This should be the same value that was passed as the first parameter tosocket(); for most purposesAF_INET will serve.

The third line is where we set the IP of the machine we need to connect to. The variabledest.sin_addr.s_addr is just an integer stored in Big Endian format, but we don't have to know that as theinet_addr() function will do the conversion from string into Big Endian integer for us.

The fourth line sets the destination port number. Thehtons() function converts the port number into a Big Endian short integer. If your program is going to be run solely on machines which use Big Endian numbers as default thendest.sin_port = 21 would work just as well. However, for portability reasonshtons() should always be used.

Now that all of the preliminary work is done, we can actually make the connection and use it:

connect(mysocket,(structsockaddr*)&dest,sizeof(structsockaddr_in));

This tells our OS to use the socketmysocket to create a connection to the machine specified indest.

len=recv(mysocket,buffer,MAXRCVLEN,0);

Now this receives up toMAXRCVLEN bytes of data from the connection and stores them in the buffer string. The number of characters received is returned byrecv(). It is important to note that the data received will not automatically be null terminated when stored in the buffer, so we need to do it ourselves withbuffer[len] = '\0'.

And that's about it!

The next step after learning how to receive data is learning how to send it. If you've understood the previous section then this is quite easy. All you have to do is use thesend() function, which uses the same parameters asrecv(). If in our previous examplebuffer had the text we wanted to send and its length was stored inlen we would writesend(mysocket, buffer, len, 0).send() returns the number of bytes that were sent. It is important to remember thatsend(), for various reasons, may not be able to send all of the bytes, so it is important to check that its return value is equal to the number of bytes you tried to send. In most cases this can be resolved by resending the unsent data.

A simple server

[edit |edit source]
#include<stdio.h>#include<stdlib.h>#include<string.h>#include<unistd.h>#include<arpa/inet.h>#include<netinet/in.h>#include<sys/socket.h>#include<pthread.h>#include<fcntl.h>#include<sys/stat.h>#include<errno.h>// --- Macros & Definitions ---#define PORTNUM 2300#define BUFSIZE 1024#define GF_FILE_NOT_FOUND 404#define GF_ERROR 500#define GF_OK 200// --- Type Declarations ---/* A minimal connection context, here simply a socket descriptor */typedefstructconnection_context{intsock;}connection_context;/* Request structure as used by the worker threads */typedefstructsteque_request{char*filepath;connection_contextcontext;}steque_request;/* A simple linked-list node for our work queue */typedefstructrequest_node{steque_request*request;structrequest_node*next;}request_node;/* The work queue structure */typedefstructrequest_queue{request_node*head;request_node*tail;}request_queue;// --- Global Variables for Multithreading ---pthread_mutex_tmutex;pthread_cond_tcond=PTHREAD_COND_INITIALIZER;request_queue*work_queue=NULL;// Global work queue pointer// --- Steque (Work Queue) Helper Functions ---/* Returns nonzero if the queue is empty */intsteque_isempty(request_queue*q){return(q->head==NULL);}/* Removes and returns the request at the front of the queue */steque_request*steque_pop(request_queue*q){if(q->head==NULL)returnNULL;request_node*node=q->head;steque_request*req=node->request;q->head=node->next;if(q->head==NULL)q->tail=NULL;free(node);returnreq;}/* Pushes (enqueues) a request onto the back of the queue */voidsteque_push(request_queue*q,steque_request*req){request_node*node=malloc(sizeof(request_node));if(!node){perror("malloc");exit(EXIT_FAILURE);}node->request=req;node->next=NULL;if(q->tail==NULL){// Queue is emptyq->head=q->tail=node;}else{q->tail->next=node;q->tail=node;}}// --- Stub Helper Functions for File Handling & Sending ---/* Clears the given buffer */voidclear_buffer(char*buf,size_tbuflen){memset(buf,0,buflen);}/* Opens the file specified by filepath; returns its file descriptor */intcontent_get(constchar*filepath){returnopen(filepath,O_RDONLY);}/* Sends a header over the connection; here it builds a simple header string */intgfs_sendheader(connection_context*ctx,intstatus,size_tsize){charheader[256];snprintf(header,sizeof(header),"Status: %d, Size: %zu\n",status,size);returnsend(ctx->sock,header,strlen(header),0);}/* Sends the buffer over the connection */ssize_tgfs_send(connection_context*ctx,constchar*buf,size_tlen){returnsend(ctx->sock,buf,len,0);}voidset_pthreads(size_tnthreads){pthread_tthreads[nthreads];intres=pthread_mutex_init(&mutex,NULL);if(res!=0)exit(92);for(inti=0;i<nthreads;i++){res=pthread_create(&threads[i],NULL,thread_handle_req,NULL);if(res!=0)exit(34);}}void*thread_handle_req(void*arg){intfd,fstats;charbuf[BUFSIZE];structstatfinfo;steque_request*req;size_ttotal_bts_sent,bts_sent,bts_read,file_len;clear_buffer(buf,BUFSIZE);for(;;){// mutex lockif(pthread_mutex_lock(&mutex)!=0)exit(12);// do nothing while queue is emptywhile(steque_isempty(work_queue))pthread_cond_wait(&cond,&mutex);// get a request from the queuereq=steque_pop(work_queue);// mutex unlockif(pthread_mutex_unlock(&mutex))exit(29);// signal to other threadspthread_cond_signal(&cond);// send file// start by getting the filepathfd=content_get(req->filepath);// check if you have found the fileif(fd==-1){gfs_sendheader(&req->context,GF_FILE_NOT_FOUND,0);break;}// file was found, now get statsfstats=fstat(fd,&finfo);// check if error when getting statsif(fstats==-1){gfs_sendheader(&req->context,GF_ERROR,0);close(fd);break;}// everything ok, send headergfs_sendheader(&req->context,GF_OK,finfo.st_size);// send everythingtotal_bts_sent=0;bts_sent=0;bts_read=0;file_len=finfo.st_size;do{clear_buffer(buf,BUFSIZE);bts_read=pread(fd,buf,BUFSIZE,total_bts_sent);if(!(bts_read>0))break;bts_sent=gfs_send(&req->context,buf,bts_read);total_bts_sent+=bts_sent;}while(file_len>total_bts_sent);// free resourcesfree(req);}free(req);return0;}// --- Main Server Code ---intmain(intargc,char*argv[]){intserver_sock,client_sock;structsockaddr_inserver_addr,client_addr;socklen_tclient_addr_len=sizeof(client_addr);// Allocate and initialize the global work queuework_queue=malloc(sizeof(request_queue));if(!work_queue){perror("malloc");exit(EXIT_FAILURE);}work_queue->head=work_queue->tail=NULL;// Set up the listening socketserver_sock=socket(AF_INET,SOCK_STREAM,0);if(server_sock<0){perror("socket");exit(EXIT_FAILURE);}memset(&server_addr,0,sizeof(server_addr));server_addr.sin_family=AF_INET;server_addr.sin_addr.s_addr=htonl(INADDR_ANY);server_addr.sin_port=htons(PORTNUM);if(bind(server_sock,(structsockaddr*)&server_addr,sizeof(server_addr))<0){perror("bind");close(server_sock);exit(EXIT_FAILURE);}if(listen(server_sock,10)<0){perror("listen");close(server_sock);exit(EXIT_FAILURE);}printf("Server listening on port %d...\n",PORTNUM);// Start worker threads (using 4 threads in this example)set_pthreads(4);// Main accept loop: for each incoming connection, create a request and enqueue itwhile(1){client_sock=accept(server_sock,(structsockaddr*)&client_addr,&client_addr_len);if(client_sock<0){perror("accept");continue;}printf("Accepted connection from %s\n",inet_ntoa(client_addr.sin_addr));// Create a new request for the connection.// Here we always send the same file ("hello.txt").steque_request*req=malloc(sizeof(steque_request));if(!req){perror("malloc");close(client_sock);continue;}req->filepath=strdup("hello.txt");if(!req->filepath){perror("strdup");free(req);close(client_sock);continue;}req->context.sock=client_sock;// Enqueue the request in a thread-safe mannerif(pthread_mutex_lock(&mutex)!=0){perror("pthread_mutex_lock");free(req->filepath);free(req);close(client_sock);continue;}steque_push(work_queue,req);pthread_cond_signal(&cond);if(pthread_mutex_unlock(&mutex)!=0){perror("pthread_mutex_unlock");continue;}}close(server_sock);return0;}

Superficially, this is very similar to the client. The first important difference is that rather than creating asockaddr_in with information about the machine we're connecting to, we create it with information about the server, and then webind() it to the socket. This allows the machine to know the data received on the port specified in thesockaddr_in should be handled by our specified socket.

Thelisten() function then tells our program to start listening using the given socket. The second parameter oflisten() allows us to specify the maximum number of connections that can be queued. Each time a connection is made to the server it is added to the queue. We take connections from the queue using theaccept() function. If there is no connection waiting on the queue the program waits until a connection is received. Theaccept() function returns another socket. This socket is essentially a "session" socket, and can be used solely for communicating with connection we took off the queue. The original socket (mysocket) continues to listen on the specified port for further connections.

Once we have "session" socket we can handle it in the same way as with the client, usingsend() andrecv() to handle data transfers.

Note that this server can only accept one connection at a time; if you want to simultaneously handle multiple clients then you'll need tofork() off separate processes, or use threads, to handle the connections.

Useful network functions

[edit |edit source]
intgethostname(char*hostname,size_tsize);

The parameters are a pointer to an array of chars and the size of that array. If possible, it finds the hostname and stores it in the array. On failure it returns -1.

structhostent*gethostbyname(constchar*name);

This function obtains information about a domain name and stores it in ahostent struct. The most useful part of ahostent structure is the(char**) h_addr_list field, which is a null terminated array of the IP addresses associated with that domain. The fieldh_addr is a pointer to the first IP address in theh_addr_list array. ReturnsNULL on failure.

FAQs

[edit |edit source]

What about stateless connections?

[edit |edit source]

If you don't want to exploit the properties of TCP in your program and would rather just use a UDP connection, then you can just replaceSOCK_STREAM withSOCK_DGRAM in your call tosocket() and use the result in the same way. It is important to remember that UDP does not guarantee delivery of packets and order of delivery, so checking is important.

If you want to exploit the properties of UDP, then you can usesendto() andrecvfrom(), which operate likesend() andrecv() except you need to provide extra parameters specifying who you are communicating with.

How do I check for errors?

[edit |edit source]

The functionssocket(),recv() andconnect() all return -1 on failure and use errno for further details.

Preprocessor directives and macrosC Programming
Networking in UNIX
X macros and serialization
Retrieved from "https://en.wikibooks.org/w/index.php?title=C_Programming/Networking_in_UNIX&oldid=4595830"
Category:

[8]ページ先頭

©2009-2025 Movatter.jp