UDP服务器请查看: 简单的UDP服务器实现
- 代码注释很详细,不了解的可以看代码,基本一半以上都是注释。
tcp_server.c (普通版)
#include<stdio.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<errno.h>
#include<unistd.h>
#include<string.h>
#include<sys/types.h>
#include<arpa/inet.h>
/*
1、创建socket
2、绑定端口号
3、把socket转换成被动模式(listen)
4、循环进行accept
5、从accept返回的new_fd中读取客户端请求
6、读取到数据后,处理数据
7、把处理后的结果返回给客户端
*/
#define _PORT_ 8080
#define _BACKLOG_ 10
int main()
{
int sock = socket(AF_INET, SOCK_STREAM, 0);
//socket()打开一个网络通讯端口,成功返回一个文件描述符
//应用程序可以像读写文件一样用read、write读写文件
//socket调用出错返回-1
//对于IPV4,family指定为AF_INET
//对于TCP协议,type为SOCK_STREAM,表示面向数据流的协议
//protocal参数指定为0即可
if(sock < 0) //失败返回-1
{
printf("create socket weeor, error is:%d, errstring is :%s\n", errno, strerror(errno));
}
struct sockaddr_in server_socket;
struct sockaddr_in client_socket;
bzero(&server_socket, sizeof(server_socket));
server_socket.sin_family = AF_INET;
server_socket.sin_addr.s_addr = htonl(INADDR_ANY);
server_socket.sin_port = htons(_PORT_);
if(bind(sock, (struct sockaddr*)&server_socket, sizeof(struct sockaddr_in) < 0))
/*
服务器端的网络地址和端口号一般不变,客户端知道了服务器IP和端口号后就可以向服务器发起连接,服务器需要调用一个bind绑定一个固定的网络IP和端口号
成功返回0,失败返回-1
bind()的作用是将参数sockfd和myaddr绑定在一起,使sockfd这个用于网络通讯的文件描述符监听myaddr所描述的地址和端口号,
struct sockaddr是一个通用指针类型,myaddr参数实际上可以接受多种协议的sockaddr结构体,
而他们的长度各不相同,所以需要第三个参数addrlen指定结构体长度
bzero(&server_socket, sizeof(server_socket));
server_socket.sin_family = AF_INET;
server_socket.sin_addr.s_addr = htonl(INADDR_ANY);
server_socket.sin_port = htons(_PORT_);
1、将整个结构体清零
2、设置地址类型为AF_INET
3、INADDR_ANY表示任意的IP地址,因为服务器可能有多个网卡,每个网卡可能绑定多个IP,这样设置,可以在所有的IP上监听,
直到与某个客户端建立了连接,才确定到底用的那个IP
4、_PORT_ 端口号
*/
{ //失败处理
printf("Bind error, error code is :%d, error string is :%s\n", errno, strerror(errno));
close(sock);
return 1;
}
if(listen(sock, _BACKLOG_) < 0)
/*
listen()声明sock_fd处于监听状态,最多允许有_BACKLOG_个客户端处于连接等待状态,如果接收到更多的连接就忽略,(注意这里设置一般不大,为5)
listen()成功返回0,失败返回-1
*/
{ //处理失败
printf("listen error\n");
return 2;
}
printf("bind and listen successed!\n");
for(;;)
{
socklen_t len = 0;
int client_sock = accept(sock, (struct sockaddr *)&client_sock, &len);
/*
三次握手完成后,服务器调用accept接受数据,
如果服务器调用accept()还没有客户端的连接请求,就阻塞等待,直到有客户端连接。
addr是一个传出参数,accept()返回时传出客户端的地址和端口号。
如果给addr参数传NULL,表示不关心客户端地址。
addrlen是一个传入,传出参数,传入是调用者提供的,缓冲区addr的长度以避免缓冲区溢出问题,传出的是客户端地址结构体的实际长的(有可能没有占满调用者提供的缓冲区)
若成功则为非负描述符,若出错则为-1
如果accept成功,那么其返回值是由内核自动生成的一个全新描述符,代表与客户端的TCP连接。一个服务器通常仅仅创建一个监听套接字,它在该服务器生命周期内一直存在。内核为每个由服务器进程接受的客户端连接创建一个已连接套接字。当服务器完成对某个给定的客户端的服务器时,相应的已连接套接字就被关闭。
*/
if(client_sock < 0)
{
printf("accept error\n");
close(sock);
return 3;
}
char buf_ip[INET_ADDRSTRLEN];
memset(buf_ip, '\0', sizeof(buf_ip));
inet_ntop(AF_INET, &client_socket.sin_addr, buf_ip, sizeof(buf_ip));
printf("get connect,ip is :%s , port is :%d \n", buf_ip, ntohs(client_socket.sin_port));
while(1)
{
char buf[1024];
memset(buf, '\0', sizeof(buf));
read(client_sock, buf, sizeof(buf));
printf("client :# %s\n", buf);
printf("server :$ ");
memset(buf, '\0', sizeof(buf));
fgets(buf, sizeof(buf), stdin);
buf[strlen(buf)-1] = '\0';
write(client_sock, buf, strlen(buf) + 1);
printf("please wait ……");
}
}
}
tcp_client.c (普通版)
#include<stdio.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<errno.h>
#include<unistd.h>
#include<string.h>
#include<sys/types.h>
#include<arpa/inet.h>
/*
1、创建socket
2、和服务器建立连接
3、给服务器发送数据
4、从服务器读取返回的结果
5、和服务器断开连接
*/
//客户端不需要固定的端口号,因此不必调用bind(),客户端的端口号由内核自动分配
#define SERVER_PORT_ 8080
#define SERVER_IP "118.24.7681"
int main(int argc, char *argv[])
{
if(argc != 2)
{
printf("Usage : client IP\n");
return 1;
}
char *str = argv[1];
char buf[1024];
memset(buf, '\0', sizeof(buf));
struct sockaddr_in server_sock;
int sock = socket(AF_INET, SOCK_STREAM, 0);
bzero(&server_sock, sizeof(server_sock));
server_sock.sin_family = AF_INET;
inet_pton(AF_INET, SERVER_IP, &server_sock.sin_addr);
server_sock.sin_port = htons(SERVER_PORT_);
int ret = connect(sock, (struct sockaddr *)&server_sock, sizeof(server_sock));
/*
调用connect连接服务器
connect和bind的参数形式一致,区别在于bind参数是自己的地址,connect参数是对方地址。
成功返回0
失败返回-1
*/
if(ret < 0)
{
printf("connect failed !!!\n");
return 1;
}
printf("connect successed ……\n");
while(1)
{
printf("client:# ");
fgets(buf, sizeof(buf), stdin);
buf[strlen(buf) - 1] = '\0';
write(sock, buf, sizeof(buf));
if(strncasecmp(buf, "quit", 4) == 0)
{
printf("quit!\n");
break;
}
printf("wait ---\n");
read(sock, buf, sizeof(buf));
printf("server $: %s", buf);
}
close(sock);
return 0;
}
- 普通TCP存在一个问题就是客户端和服务器只能一对一绑定,多个客户端不能同时访问一个服务器,所以便有了多进程版的服务器。
tcp_server.c (多进程版)
#include <stdio.h>
#include <sys/types.h> //
#include <sys/socket.h> // socket() bind()
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <netinet/in.h>
static void usage(const char* proc)
{
printf("use help : %s [local_ip] [local_port]\n", proc);
}
// 创建并绑定一个socket
int startup(const char* _ip, int _port)
{
// 创建一个socket
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) //创建失败处理
{
perror("----sock----fail\n");
exit(-1);
}
// 绑定一个本地socket
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_addr.s_addr = inet_addr(_ip);
local.sin_port = htons(_port);
//绑定端口号
if(bind(sock, (struct sockaddr*)&local, sizeof(local)) != 0)
{
perror("---bind---fail\n");
close(sock);
exit(-2);
}
//监听
if(listen(sock, 5) != 0)
{
perror("----listen----fail\n");
close(sock);
exit(-1);
}
return sock;
}
int main(int argc, char**argv)
{
if(argc != 3)
{
usage(argv[0]);
return -1;
}
// 获取一个local socket
int listen_sock = startup(argv[1], atoi(argv[2]));
struct sockaddr_in client;
socklen_t len = sizeof(client);
char buf[1024];
while(1)
{
int new_sock = accept(listen_sock,(struct sockaddr*)&client, &len);
if(new_sock < 0)
{
perror("----accept----fail\n");
close(listen_sock);
exit(-5);
}
pid_t id = fork();
if(id == 0)
{
//可以再这里fork 然后退出子进程,这样负责监听的进程PID就不会一直变
close(listen_sock);
// 子进程已经获得 sockfd, 不需要listen sock所以关闭
while(1)
{
ssize_t s = read(new_sock,buf, sizeof(buf)-1);
if(s > 0 )
{
buf[s] = 0;
printf("client say## %s \n", buf);
write(new_sock, buf, strlen(buf));
}
else if( s == 0)
{
printf("client quit. \n");
break;
}
else
break;
}
exit(0);
}
else if (id > 0)
{
// 父进程
// 在父进程中fork退出的话,会导致监听进程的pid一直在变,所以我们也可以再子进程中fork然后退出
// 此时子进程已经获取 sockfd, 父进程只需要监听sockfd,
//又因为每次fork都会赋值描述符,为了节省资源,关闭new_sock
if(fork() > 0)
{
exit(0);
}
// 父进程退出,此时第二次fork出的子进程 成为孤儿进程,此后它产生的子进程退出时由系统来回收资源
}
}
return 0;
}
在server.c中,我们先fock了两个进程父进程和子进程。父进程的工作是不断地返回accept处拿new_sock,子进程的工作是不断地去处理new_sock。也就是说父进程只管拿,子进程只管处理。所以父进程关闭不需要的套接字new_sock,而只留下listen_sock ; 子进程关闭不需要的listen_sock,而留下new_sock.
为什么还在子进程中再次fock创建一个孙子进程?这也正是这段代码的巧妙之处。
通过之前的学习我们知道,子进程退出后变成僵尸进程,直到父进程调用wait或者waitpid来对子进程资源进行回收。所以在第一次fork完成后,我们父进程会调用waitpid等待函数去等待子进程结束,但是它目前是阻塞式等待。为了解决阻塞问题,我们在子进程中又创建了一个孙子进程,并且让子进程退出,让孙子进程去完成子进程的工作。而对于孙子进程来说,子进程的退出使孙子进程变成孤儿进程,被1号进程Init收养,若孙子进程退出也是由Init进程回收它,与父进程中的waitpid无关。这样当子进程退出时,父进程的waitpid对子进程资源进行了回收,并且回收后因为子进程已经不存在了,waitpid也失去了它的作用,就不会阻塞了。这就是这段代码的巧妙之处。多线程版也是为了支持多连接。类似多进程。
tcp_server.c (多线程版)
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<fcntl.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<pthread.h>
static void Usage(const char* proc)
{
printf("Usage:%s[local_ip][local_port]\n",proc);
}
int StartUp(const char* ip,int port)
{
int sock = socket(AF_INET,SOCK_STREAM,0);//create socket
if(sock < 0){
perror("socket");
exit(2);
}
struct sockaddr_in local;//IPV4
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = inet_addr(ip);
if(bind(sock,(struct sockaddr*)&local,sizeof(local)) < 0){//bind
perror("bind");
exit(3);
}
if(listen(sock,10) < 0){//listen
perror("listen");
exit(4);
}
return sock;//return an "listen_sock";
}
void* request(void* arg)
{
int new_sock = (int)arg;
printf("get a new client\n");
while(1)
{//first read,then write
char buf[1024];
ssize_t s = read(new_sock,buf,sizeof(buf)-1);
if(s > 0){//read successful
buf[s] = '\0';
printf("client#:%s\n",buf);
write(new_sock,buf,strlen(buf));//server hui xian
}
else if(s == 0){//read to file's end
printf("client exit\n");
break;
}
else{//read fail
perror("read");
break;
}
}
}
int main(int argc,char* argv[])
{
if(argc != 3){
Usage(argv[0]);
return 1;
}
int listen_sock = StartUp(argv[1],atoi(argv[2]));//get listen_sock
while(1)
{
struct sockaddr_in client;//get sender's information
socklen_t len = sizeof(client);
//get real socket that we will operator
int new_sock = accept(listen_sock,(struct sockaddr*)&client,&len);
if(new_sock < 0){
perror("accept");//if fail,again listen
continue;
}
pthread_t id;
pthread_create(&id,NULL,&request,(void*)new_sock);
pthread_detach(id);
}
return 0;
}