Writing the first TCP server: achieving one-to-one connection communication
I. Introduction
Teach you step by step how to write a TCP server program from scratch, and experience how you can build a building with only one brick at the beginning .
In order to avoid being too long and boring the reader, [TCP server development] is implemented in stages and optimized and upgraded step by step.
2. APIs that need to be used
2.1. socket() function
Function prototype:
#include<sys/types.h>
#include<sys/socket.h>
int socket(int domain, int type, int protocol);
This function creates a socket file descriptor with protocol family, protocol type, and protocol number. If the function call is successful, it will return a file descriptor identifying the socket. If it fails, it will return -1 and set errno.
Domain parameter value meaning:
name | meaning |
---|---|
PF_UNIX,PF_LOCAL | local communication |
AF_INET,PF_INET | IPv4 protocol |
PF_INET6 | IPv6 protocol |
PF_NETLINK | kernel user interface device |
PF_PACKET | Low-level package access |
Type parameter value meaning:
name | meaning |
---|---|
SOCK_STREAM | TCP connection provides a serialized, reliable, bidirectional connection byte stream. Supports out-of-band data transfer |
SOCK_DGRAM | UDP connection |
SOCK_SEQPACKET | Serialization package provides a serialized, reliable, two-way data transmission channel with constant data length. Every time the read system call is called, all data needs to be read out. |
:SOCK_PACKET | Special type |
SOCK_RDM | Provide reliable data packets, but do not guarantee that the data is in order |
SOCK_RAW | Provides raw network protocol access |
Meaning of the protocol parameter:
Usually there is only one specific type in a protocol, so the protocol parameter can only be set to 0; if the protocol has multiple specific types, you need to set this parameter to select a specific type.
2.2. bind() function
Function prototype:
#include<sys/types.h>
#include<sys/socket.h>
int bind(int sockfd, const struct sockaddr *my_addr, socklen_t addrlen);
Parameter Description:
- The first parameter sockfd is the file descriptor created with the socket() function.
- The second parameter my_addr is a pointer to a structure of sockaddr parameters. sockaddr contains address, port and IP address information.
- The third parameter addrlen is the length of the my_addr structure, which can be set to sizeof(struct sockaddr).
When the return value of the bind() function is 0, it means the binding is successful, and -1 means the binding failed and errno is set.
2.3. listen() function
Function prototype:
#include<sys/socket.h>
int listen(int sockfd, int backlog);
Parameter Description:
- The first parameter sockfd is the file descriptor created with the socket() function.
- The second parameter backlog specifies the maximum number of connections that the kernel should queue for the corresponding socket.
2.4. accept() function
Function prototype:
#include<sys/types.h>
#include<sys/socket.h>
int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen);
Parameter Description:
- sockefd: Socket descriptor that listens for connections after listen().
- addr: (optional) pointer. Pointer to a buffer that receives the addresses of connected entities known to the communication layer. The actual format of the Addr parameter is determined by the address family generated when the socket was created.
- addrlen: (optional) pointer. Input parameter, used together with addr, points to an integer holding the length of addr address.
2.5. recv() function
Function prototype:
#include<sys/types.h>
#include<sys/socket.h>
int recv( int fd, char *buf, int len, int flags);
Parameter Description:
- The first parameter specifies the receiving socket descriptor;
- The second parameter specifies a buffer, which is used to store the data received by the recv function;
- The third parameter specifies the length of buf;
- The fourth parameter is generally set to 0.
return value:
- Returns a number greater than 0, indicating the introduced data size.
- Returns 0, indicating that the connection is disconnected.
- Returns -1, indicating an error in accepting data.
2.6. send() function
Function prototype:
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
Parameter Description:
- sockfd: Send data to the socket
- buf: the first address of the data to be sent
- len: bytes of data to be sent
- int flags: When set to MSG_DONTWAITMSG, it means non-blocking. When set to 0, the function is the same as write.
Return value: The actual number of bytes sent is returned on success, -1 is returned on failure and errno is set.
2.7. strerror() function
strerror()
The function returns a pointer to a string describing the error code passed in parameter errnum, possibly using the LC_MESSAGES part of the current locale to select the appropriate language. (For example, if errnum is EINVAL, the returned description will be "Invalid Parameter".) This string cannot be modified by the application, but can be modified by subsequent calls to strerror()
or strerror_l()
. perror()
This string will not be modified by any other library functions, including
Function prototype:
#include <string.h>
char *strerror(int errnum);
int strerror_r(int errnum, char *buf, size_t buflen);
/* XSI-compliant */
char *strerror_r(int errnum, char *buf, size_t buflen);
/* GNU-specific */
char *strerror_l(int errnum, locale_t locale);
3. Implementation steps
One-to-one server design:
(1) Create socket.
int listenfd=socket(AF_INET,SOCK_STREAM,0);
if(listenfd==-1){
printf("errno = %d, %s\n",errno,strerror(errno));
return SOCKET_CREATE_FAILED;
}
(2) Binding address.
struct sockaddr_in server;
memset(&server,0,sizeof(server));
server.sin_family=AF_INET;
server.sin_addr.s_addr=htonl(INADDR_ANY);
server.sin_port=htons(LISTEN_PORT);
if(-1==bind(listenfd,(struct sockaddr*)&server,sizeof(server))){
printf("errno = %d, %s\n",errno,strerror(errno));
close(listenfd);
return SOCKET_BIND_FAILED;
}
(3) Set up monitoring.
if(-1==listen(listenfd,BLOCK_SIZE)){
printf("errno = %d, %s\n",errno,strerror(errno));
close(listenfd);
return SOCKET_LISTEN_FAILED;
}
(4) Receive connection.
struct sockaddr_in client;
memset(&client,0,sizeof(client));
socklen_t len=sizeof(client);
int clientfd=accept(listenfd,(struct sockaddr*)&client,&len);
if(clientfd==-1){
printf("errno = %d, %s\n",errno,strerror(errno));
close(listenfd);
return SOCKET_ACCEPT_FAILED;
}
(5) Receive data.
char buf[BUFFER_LENGTH]={
0};
ret=recv(clientfd,buf,BUFFER_LENGTH,0);
if(ret==0) {
printf("connection dropped\n");
}
printf("recv --> %s\n",buf);
(6) Send data.
if(-1==send(clientfd,buf,ret,0))
{
printf("errno = %d, %s\n",errno,strerror(errno));
}
(7) Close the file descriptor.
close(clientfd);
close(listenfd);
4. Complete code
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#define LISTEN_PORT 8888
#define BLOCK_SIZE 10
#define BUFFER_LENGTH 1024
enum ERROR_CODE{
SOCKET_CREATE_FAILED=-1,
SOCKET_BIND_FAILED=-2,
SOCKET_LISTEN_FAILED=-3,
SOCKET_ACCEPT_FAILED=-4
};
int main(int argc,char **argv)
{
// 1.
int listenfd=socket(AF_INET,SOCK_STREAM,0);
if(listenfd==-1){
printf("errno = %d, %s\n",errno,strerror(errno));
return SOCKET_CREATE_FAILED;
}
// 2.
struct sockaddr_in server;
memset(&server,0,sizeof(server));
server.sin_family=AF_INET;
server.sin_addr.s_addr=htonl(INADDR_ANY);
server.sin_port=htons(LISTEN_PORT);
if(-1==bind(listenfd,(struct sockaddr*)&server,sizeof(server))){
printf("errno = %d, %s\n",errno,strerror(errno));
close(listenfd);
return SOCKET_BIND_FAILED;
}
// 3.
if(-1==listen(listenfd,BLOCK_SIZE)){
printf("errno = %d, %s\n",errno,strerror(errno));
close(listenfd);
return SOCKET_LISTEN_FAILED;
}
// 4.
struct sockaddr_in client;
memset(&client,0,sizeof(client));
socklen_t len=sizeof(client);
int clientfd=accept(listenfd,(struct sockaddr*)&client,&len);
if(clientfd==-1){
printf("errno = %d, %s\n",errno,strerror(errno));
close(listenfd);
return SOCKET_ACCEPT_FAILED;
}
printf("client fd = %d\n",clientfd);
int ret=1;
while(ret>0){
// 5.
char buf[BUFFER_LENGTH]={
0};
ret=recv(clientfd,buf,BUFFER_LENGTH,0);
if(ret==0) {
printf("connection dropped\n");
break;
}
printf("recv --> %s\n",buf);
if(-1==send(clientfd,buf,ret,0))
{
printf("errno = %d, %s\n",errno,strerror(errno));
}
}
close(clientfd);
close(listenfd);
return 0;
}
Compile command:
gcc -o server server.c
5. TCP client
5.1. Implement a TCP client yourself
Implement the code for a TCP client to connect to the TCP server yourself:
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#define BUFFER_LENGTH 1024
enum ERROR_CODE{
SOCKET_CREATE_FAILED=-1,
SOCKET_CONN_FAILED=-2,
SOCKET_LISTEN_FAILED=-3,
SOCKET_ACCEPT_FAILED=-4
};
int main(int argc,char** argv)
{
if(argc<3)
{
printf("Please enter the server IP and port.");
return 0;
}
printf("connect to %s, port=%s\n",argv[1],argv[2]);
int connfd=socket(AF_INET,SOCK_STREAM,0);
if(connfd==-1)
{
printf("errno = %d, %s\n",errno,strerror(errno));
return SOCKET_CREATE_FAILED;
}
struct sockaddr_in serv;
serv.sin_family=AF_INET;
serv.sin_addr.s_addr=inet_addr(argv[1]);
serv.sin_port=htons(atoi(argv[2]));
socklen_t len=sizeof(serv);
int rwfd=connect(connfd,(struct sockaddr*)&serv,len);
if(rwfd==-1)
{
printf("errno = %d, %s\n",errno,strerror(errno));
close(rwfd);
return SOCKET_CONN_FAILED;
}
int ret=1;
while(ret>0)
{
char buf[BUFFER_LENGTH]={
0};
printf("Please enter the string to send:\n");
scanf("%s",buf);
send(connfd,buf,strlen(buf),0);
memset(buf,0,BUFFER_LENGTH);
printf("recv:\n");
ret=recv(connfd,buf,BUFFER_LENGTH,0);
printf("%s\n",buf);
}
close(rwfd);
return 0;
}
Compile:
gcc -o client client.c
5.2. You can use the NetAssist network assistant tool under Windows
Download address: http://old.tpyboard.com/downloads/NetAssist.exe
summary
At this point, we have achieved a one-to-one server connection. The TCP server code at this stage can only receive one client access. If the client is disconnected, it will exit directly. The focus is on mastering the basic process of developing a TCP server. The next chapter will introduce upgrading on this basis to accept simultaneous access from multiple clients.