Feed
Have learned many times computer network, still do not network programming.
This will not be the case after reading this post.
Environment: Ubuntu14.04 64bit
What is a socket
Is the concept of data communication used in network application programming based on TCP/IP, often referred to as a "socket", which is used to describe IP addresses and ports, and is a handle to a communication chain. Hosts on the internet typically run multiple service software, while providing several services. Each service opens a socket and binds to a port, and the different ports correspond to different services. The socket is like a porous socket, as its English intended. A host is like a room full of various sockets, each outlet has a number, some sockets provide 220 vac, some provide 110 volts AC, some provide cable TV programs. Customer software plug into different numbered sockets, you can get different services.
Network program based on TCP protocol
is the general flow of client/server programs based on the TCP protocol:
With this diagram, we can clearly relate some of the sockets ' interfaces to the server and client state transitions in TCP/IP.
Readiness Status
After the server calls the socket (), bind (), listen () to complete the initialization, call accept () block wait, in the state of the listening port, after the client calls the socket () initialization, call Connect () to emit a SYN segment and block waiting for the server to answer, The server answers a syn-ack segment, and the client receives it back from connect (), while answering an ACK segment, which the server receives and returns from accept ().
The process of data transfer
After the connection is established, the TCP protocol provides full-duplex communication service, but the general client/server process is initiated by the client, the server passively processes the request, and a question-and-answer method. Therefore, the server calls read () immediately after it returns from accept (), reads the socket like a read pipeline, if no data arrives to block the wait, then the client calls write () to send the request to the server, the server receives the request from read () to process the client, During this time the client calls read () blocking the server's answer, the server calls write () sends the processing results back to the client, calls the read () block again to wait for the next request, the client receives the next request from Read (), and then loops down.
If the client does not have more requests, call Close () to close the connection, just as the write-side closes the pipe, and the server's read () returns 0, so the server knows that the client closed the connection and also called Close () to close the connection. Note that after either party calls Close (), the two transmission directions of the connection are closed and no more data can be sent. If a party calls shutdown () The connection is in a semi-closed state and can still receive data from the other side.
When learning the socket API, be aware of how the application and the TCP protocol layer interact: * What the TCP protocol layer does when an application calls a socket function, such as calling Connect () emits a SYN segment * application How to know the state change of the TCP protocol layer For example, returning from a blocked socket function indicates that the TCP protocol received some segments, such as read () returns 0 indicating that the fin segment was received.
Code Brick Start
Run the code before you explain it.
Service side, SERVER.C
/* SERVER.C */#include <stdio.h> #include <stdlib.h> #include <ctype.h> #include <string.h># Include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h># Define MAXLINE 80#define serv_port 8000int Main (void) {struct sockaddr_in servaddr, cliaddr;socklen_t cliaddr_len;int LISTENFD, Connfd;char Buf[maxline];char str[inet_addrstrlen];int i, N;LISTENFD = socket (af_inet, SOCK_STREAM, 0); Bzero ( &SERVADDR, sizeof (SERVADDR)); servaddr.sin_family = Af_inet;servaddr.sin_addr.s_addr = htonl (INADDR_ANY); Servaddr.sin_port = htons (Serv_port); Bind (LISTENFD, (struct sockaddr *) &servaddr, sizeof (SERVADDR)); listen (LISTENFD);p rintf ("accepting Connections \ n "); while (1) {Cliaddr_len = sizeof (CLIADDR); connfd = Accept (LISTENFD, (struct sockaddr *) &cliaddr, & Amp;cliaddr_len); n = Read (CONNFD, buf, MAXLINE);p rintf ("received from%s at PORT%d\n", Inet_ntop (Af_inet, &cliaddr.sin_addr, str , sizeof (str)), nTohs (Cliaddr.sin_port)); for (i = 0; i < n; i++) Buf[i] = ToUpper (buf[i]); write (CONNFD, buf, N); close (CONNFD);}}
Client, client.c
/* client.c */#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h># Include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #define MAXLINE 80#define serv_ PORT 8000int Main (int argc, char *argv[]) {struct sockaddr_in servaddr;char buf[maxline];int sockfd, N;char *str; if (argc! = 2) {fputs ("Usage:./client message\n", stderr); exit (1);} str = argv[1]; SOCKFD = socket (af_inet, sock_stream, 0) bzero (&servaddr, sizeof (SERVADDR)); servaddr.sin_family = af_inet;inet_ Pton (Af_inet, "127.0.0.1", &servaddr.sin_addr); servaddr.sin_port = Htons (serv_port); Connect (SOCKFD, (struct sockaddr *) &servaddr, sizeof (SERVADDR)); write (SOCKFD, str, strlen (str)); n = Read (SOCKFD, BUF, MAXLINE);p rintf ("Response from server:\n"); Write (Stdout_fileno, buf, N); close (SOCKFD); return 0;}
Compile
GCC client.c-o CLIENTGCC server.c-o Server
Run results
Start with an explanation from a file
The interfaces defined in Unistd.h-unistd.h are typically a large number of packages (English: Wrapper functions) for system calls, such as fork, pipe, and various I/O primitives (read, write, close, and so on).
Sys/socket.h-Provides socket functions and data structures.
Netinet/in.h-Defines the data structure sockaddr_in, which contains the address format definitions for IPV4 and IPV6.
Arpa/inet.h-provides IP address translation functions, such as the conversion of the size end, and the transformation of the expression of the IP address.
Look at the main function of the server.
16 lines, declaring two socketaddr_in structures. The IPV4 address is represented by a sockaddr_in structure, including a 16-bit port number and 32-bit IP address. The IPV6 address is represented by a SOCKADDR_IN6 structure, including 16-bit port numbers, 128-bit IP addresses, and some control fields.
17 rows, declare a variable that records the length of the client address, socklen_t is the type used in the socket to represent the length. LInus Torvalds (he wants to have more people, but obviously not many) trying to explain to them that using size_t is completely wrong, because the length of size_t and int is not the same in a 64-bit structure, and this parameter (that is, the third parameter of the Accept function) Must be the same length as int, as this is the BSD socket interface standard. Eventually POSIX's gang found a solution, which was to create a new type of "socklen_t".
23 Lines, open a network communication port, the function prototype is:
int socket (int family, int type, int protocol);
The socket () opens a network communication port and, if successful, returns a file descriptor like open (), where the application can send and receive data on the network like a read-write file, and returns 1 if the socket () call error occurs. read/write For the ipv4,family parameter, specify Af_inet. For the TCP protocol, the type parameter is specified as Sock_stream, which represents the stream-oriented transport protocol.
25-28 lines, the entire structure is cleared first, and then set the address type Af_inet, the network address is Inaddr_any, this macro represents any local IP address, because the server may have more than one network card, each network card may also bind multiple IP addresses, This setting can be monitored on all IP addresses until a connection is made to a client to determine which IP address to use, the port number is serv_port, and we define 8000.
30 lines, call bind bind a fixed network address and port number. Bind () successfully returns 0, and the failure returns-1. Function Prototypes:
int bind (int sockfd, const struct SOCKADDR *myaddr, socklen_t Addrlen);
The role of BIND () is to bind the parameters sockfd and myaddr together so that sockfd this file descriptor for network traffic listens to the address and port number described by MYADDR.
32 lines, declares that LISTENFD is in a listening state, and allows up to 20 clients to be in a connected reception state, ignoring if more connection requests are received. Listen () successfully returned 0, failed to return-1.
25-49 lines, the whole is a while dead loop, each cycle processing a client connection. Since Cliaddr_len is an incoming outgoing parameter, the initial value should be re-assigned before each call to accept (). The parameter listenfd of the Accept () is the previous listener file descriptor, and the return value of accept () is another file descriptor CONNFD, which then communicates with the client through this CONNFD, and finally closes CONNFD disconnects without closing LISTENFD Again, back to the beginning of the loop LISTENFD is still used as the accept parameter. Accept () successfully returns a file descriptor with an error of 1.
The client has less bind and more connect functions. Because the client does not need a fixed port number, bind () does not have to be called, and the client's port number is automatically assigned by the kernel. Note that the client is not allowed to call bind (), but it is not necessary to call bind () to fix a port number, and the server does not have to call bind (), but if the server does not call bind (), the kernel automatically assigns a listening port to the server, and the port number is different each time the server is started. The client is having trouble connecting to the server. Connect function Prototype:
int connect (int sockfd, const struct SOCKADDR *servaddr, socklen_t Addrlen);
The client needs to call Connect () to the server, and the parameters of connect and bind are the same, except that the bind parameter is its own address, and the parameter of Connect is the address of the other. Connect () successfully returned 0, error returned-1.
A little package, please.
Although the code can work, but look at a bit of egg pain, and the entire code has a process error, errors are also difficult to deal with, first of all, the socket related functions encapsulated once,
Wrap.h
#include <stdlib.h> #include <sys/socket.h> #include <errno.h>//print error message and exit.void Perr _exit (const char *s);//server Accept message from Client.int accept (int fd, struct sockaddr *sa, socklen_t *salenptr); void Bind (int fd, const struct sockaddr *sa, socklen_t salen), void Connect (int fd, const struct sockaddr *sa, Socklen_ T salen), void Listen (int fd, int backlog), int Socket (int family, int type, int protocol), ssize_t Read (int fd, void *ptr, S ize_t nbytes); ssize_t readn (int fd, void *vptr, size_t n); ssize_t Write (int fd, const void *ptr, size_t nbytes); ssize_t Wr Iten (int fd, const void *vptr, size_t n), static ssize_t my_read (int fd, char *ptr), ssize_t Readline (int fd, void *vptr, si ze_t maxlen); void Close (int fd);
Wrap.c
#include "wrap.h"//print error message and exitvoid perr_exit (const char *s) {perror (s); exit (1);} Server Accept Message from Client.int accept (int fd, struct sockaddr *sa, socklen_t *salenptr) {int n;again:if (n = accep T (FD, SA, salenptr)) < 0) {if (errno = = econnaborted) | | (errno = = eintr)) Goto Again;elseperr_exit ("Accept Error");} return n;} void Bind (int fd, const struct SOCKADDR *sa, socklen_t salen) {if (Bind (FD, SA, Salen) < 0) {Perr_exit ("bind error");}} void Connect (int fd, const struct SOCKADDR *sa, socklen_t salen) {if (Connect (FD, SA, Salen) < 0) {perr_exit ("Connet Erro R ");}} void Listen (int fd, int backlog) {if (Listen (FD, Backlog) < 0) {perr_exit ("Listen error");}} int Socket (int family, int type, int protocol) {int n;if (n = Socket (family, type, protocol)) < 0) Perr_exit ("Socket Erro R "); return n;} ssize_t Read (int fd, void *ptr, size_t nbytes) {ssize_t N;again:if ((n = Read (fd, PTR, nbytes)) = =-1) {if (errno = = eintr) got o again;elsereturn-1;} return n;} ssize_t readn (int fd, VOID *vptr, size_t N) {size_t nleft;ssize_t Nread;char *ptr;ptr = Vptr;nleft = N;while (Nleft > 0) {if (nread = Read (FD, PTR, nleft)) < 0) {if (errno = = eintr) nread = 0;elsereturn-1;} else if (nread = = 0) Break;nleft-= nread;ptr + = nread;} return n-nleft;} ssize_t Write (int fd, const void *ptr, size_t nbytes) {ssize_t N;again:if ((n = Write (fd, PTR, nbytes)) = =-1) {if (errno = = E INTR) goto again;elsereturn-1;} return n;} ssize_t writen (int fd, const void *vptr, size_t n) {size_t nleft;ssize_t nwritten;const char *ptr;ptr = Vptr;nleft = N;whil E (Nleft > 0) {if (Nwritten = write (FD, PTR, nleft) <= 0) {if (Nwritten < 0 && errno = = eintr) {nwritten = 0; }else{return-1;}} Nleft-= nwritten;ptr + = Nwritten;} return n;} static ssize_t my_read (int fd, char *ptr) {static int read_cnt;static char *read_ptr;static char read_buf[100];if (read_cnt <= 0) {again:if ((read_cnt = Read (fd, read_buf, sizeof (READ_BUF))) < 0) {if (errno = = eintr) goto again;return-1;} else if (read_cnt= = 0) return 0;read_ptr = Read_buf;} Read_cnt--;*ptr = *read_ptr++;return 1;} ssize_t Readline (int fd, void *vptr, size_t maxlen) {ssize_t n, rc;char c, *ptr;ptr = vptr;for (n = 1; n < maxlen; n+ +) {if (rc = My_read (FD, &c) = = 1) {*ptr++ = c;if (c = = ' \ n ') break;} else if (rc = = 0) {*ptr = 0;return n-1;} el sereturn-1;} *ptr = 0;return N;} void Close (int fd) {if (Close (FD) = =-1) {Perr_exit ("close Error");}}
The error handling here mainly uses the Erron and PERR functions.
When the C API function in Linux exception, the errno variable (need include errno.h) is usually assigned an integer value, different values represent different meanings, you can see the reason for this value speculation, in the actual programming with this trick to solve a lot of the original seemingly inexplicable problems. But errno is a number, representing the specific meaning of the errno.h to read the macro definition, and each review is a very tedious thing. There are several ways to easily get the error message
(1) void perror (const char *s)
Function description
Perror () is used to output the cause of an error in the previous function to a standard error (STDERR), and the string referred to in parameter S is printed first, followed by the error reason string. The cause of this error is in accordance with global variables
The errno value to determine the string to output.
(2) Char *strerror (int errno)
Converts the error code to a string error message, which can be combined to output the string and other information to the user interface such as
fprintf (stderr, "Error in CreateProcess%s, Process ID%d
", Strerror (errno), ProcessID)
Note: Suppose ProcessID is an ID that has been acquired
(3) printf ("%m", errno);
It's not all the time. Error codes can be obtained by using error, such as the following code snippet
Change the client to a mode that can send messages multiple times after a successful connection.
Client.c
/* CLIENT.C */#define MAXLINE 80#define serv_port 8000int Main (int argc, char *argv[]) {struct sockaddr_in Servaddr;char bu F[maxline];int sockfd, N;char *str; SOCKFD = Socket (af_inet, sock_stream, 0) bzero (&servaddr, sizeof (SERVADDR)); servaddr.sin_family = af_inet;inet_ Pton (Af_inet, "127.0.0.1", &servaddr.sin_addr); servaddr.sin_port = Htons (serv_port); Connect (SOCKFD, (struct sockaddr *) &servaddr, sizeof (SERVADDR)), while (Fgets (buf, MAXLINE, stdin)! = NULL) {Write ( SOCKFD, buf, strlen (BUF)), n = Read (SOCKFD, buf, MAXLINE), if (n = = 0) {printf ("The other side have been closed.\n");} Else{write (Stdout_fileno, buf, n);}} Close (SOCKFD); return 0;}
SEVER.C also makes a bit of modification so that it can handle requests from the same client multiple times.
/* SERVER.C */#define MAXLINE 80#define serv_port 8000int Main (void) {struct sockaddr_in servaddr, cliaddr;socklen_t cliad Dr_len;int listenfd, Connfd;char buf[maxline];char str[inet_addrstrlen];int i, N;LISTENFD = Socket (AF_INET, SOCK_STREAM , 0); Bzero (&servaddr, sizeof (SERVADDR)); servaddr.sin_family = Af_inet;servaddr.sin_addr.s_addr = Htonl (INADDR_ any); Servaddr.sin_port = Htons (Serv_port); Bind (LISTENFD, (struct sockaddr *) &servaddr, sizeof (SERVADDR)); Listen (LISTENFD);p rintf ("accepting connections ... \ n"), while (1) {Cliaddr_len = sizeof (CLIADDR); connfd = Accept ( LISTENFD, (struct sockaddr *) &cliaddr, &cliaddr_len), while (1) {n = Read (CONNFD, buf, MAXLINE), if (n = = 0) {printf (" The other side have been closed!\n "); printf ("received from%s at PORT%d\n", Inet_ntop (Af_inet, &cliaddr.sin_addr, str, sizeof (STR)), Ntohs (CLI Addr.sin_port)); for (i = 0; i < n; i++) Buf[i] = ToUpper (Buf[i]); Write (CONNFD, buf, n);} Close (CONNFD);}}
Run results
Small jobs-Simple Web server
Follow the steps below:
1. Terminal input ifconfig, to see their own IP address;
2. Run the above compiled server program;
3. Type in the address bar of the browser: x.x.x.x:80
(x.x.x.x is your IP address)
The result of getting the browser running is
This is a simple HTTP request.
In this communication process, the server is our server program, the browser is the client. After the server starts, listen to the corresponding port, then enter the local IP and port in the browser, then the browser sends the HTTP protocol header to the server, the processing in the server is to change all the letters to uppercase, and then write back to the socket, What the client finally gets is the ToUpper request message.
We implement the Web server as long as the first line can be parsed correctly, this is a GET request, the root directory of the service directory/(in this case, in fact,/var/www), the Web server should be the index page in the directory (the default is index.html) to the browser, that is, the/ Var/www/index.html is sent to the browser. If the contents of the file are as follows (the HTML file does not need to be "\ r \ n" to wrap, "\ n" to line up). Simple index.html.
The data returned by the server ishttp/1.1 okcontent-type:text/html
The HTTP header that the server answers is also the end of each line with a carriage return and a newline ending followed by a carriage return with a blank line.
The first line of the HTTP header is the protocol version and the answer code, 200 indicates success, the following message OK can actually write freely, the browser is not concerned about, mainly for debugging when the developer to see. Although the network protocol is ultimately the dialogue between the program and the program, but in the development process is a dialogue between people and programs, a design transparent network protocol can provide a lot of intuitive information to developers, so many application layer network protocols, such as HTTP, FTP, SMTP, POP3, etc. are text-based protocols For transparency (transparency).
The second line of the HTTP header indicates the type of file to be sent (called the MIME type), here is the text/html, the plain text file is Text/plain, the picture is Image/jpg, image/png, and so on.
Then send the contents of the file, after sending the active close connection, so that the browser will know that the file has been sent. This is very special: usually the network communication is the client initiates the connection, initiates the request, actively shuts down the connection, the server only passively handles the various situations, and the HTTP protocol specifies that the server actively shuts down the connection (some Web servers can be configured as keep-alive, we do not discuss this situation).
The final implementation of the server code is as follows
/* SERVER.C */#include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <string.h># Include <unistd.h> #include <netinet/in.h> #include <arpa/inet.h> #include "wrap.h" #define MAXLINE 80void loadconfig (int *port, char *path); int main (void) {struct sockaddr_in servaddr, cliaddr;socklen_t cliaddr_len;int LISTENFD, Connfd;char Buf[maxline];char str[inet_addrstrlen];int i, n;int pd_index,ret;int port;char *path = NULL; Loadconfig (&port, path); listenfd = Socket (af_inet, sock_stream, 0); int opt=1; SetSockOpt (listenfd,sol_socket,so_reuseaddr,&opt,sizeof (opt)); Bzero (&servaddr, sizeof (SERVADDR)); servaddr.sin_family = Af_inet;servaddr.sin_addr.s_addr = htonl (inaddr_any); servaddr.sin_port = htons (port); Bind (LISTENFD, (struct sockaddr *) &servaddr, sizeof (SERVADDR)); Listen (LISTENFD);p rintf ("accepting connections ... \ n"), while (1) {Cliaddr_len = sizeof (CLIADDR); connfd = Accept ( LISTENFD, (struct sockaddr *) &cliaddr, &cliaddr_len); while (1) {n = Read (CONNFD, buf, MAXLINE), if (n = = 0) {printf ("The other side have been closed!\n"); printf ("received from%s at PORT%d\n", Inet_ntop (Af_inet, &cliaddr.sin_addr, str, sizeof (STR)), Ntohs (CLI Addr.sin_port)); i=0; Char title[15]; while (buf[i]!= ' \ R ') {Title[i]=buf[i]; i++; }title[i]= ' + '; if (0 = = (strcmp (title, "get/http/1.1"))) {bzero (buf,maxline); strcpy (buf, "http/1.1 0k\r\n"); Write (Connfd,buf,strlen (BUF)); Bzero (Buf,maxline); strcpy (buf, "conent_type:text/html\r\n"); Write (Connfd,buf,strlen (BUF)); Bzero (Buf,maxline); Buf[0]= ' \ r '; Buf[1]= ' \ n '; Write (connfd,buf,2); Pd_index=open ("/var/www/index.html", o_rdonly); if (pd_index<0) {perr_exit ("Open File failed.\n"); } bzero (Buf,maxline); while ((Ret=read (pd_index,buf,maxline)) >0) {write (Connfd,buf,ret); printf ("Send index data...\n"); Bzero (Buf,maxline);}}} Close (CONNFD);}} void Loadconfig (int *port, char *path){FILE *FD;FD = fopen ("/etc/myhttpd.conf", "R"), if (FD < 0) perr_exit ("Can ' t load config.\n"), fscanf (FD, "port=%d\ ndirectory=%s ", port, Path);}
Final Run Results
ReferenceLinux C Programming One-stop learning-http://c4linux.letaoba.info/
Implement a simple Web server under Ubuntu-http://www.linuxidc.com/Linux/2013-10/91160.htm
Linux network programming One-stop learning