在TCP/IP协议中,“IP地址+TCP或UDP端口号”唯一标识网络通讯中的一个进程,“IP地址+端口号”称为(套接字)socket。在TCP协议中,建立连接的两个进程各自有一个socket来标识,这两个socket组成的socket pair就唯一标识一个连接。
TCP/IP协议最早在BSD UNIX上实现,为TCP/IP协议设计的应用层编程接口称为socket API。
此处使用socket API应用层编程接口来实现多进程多线程的TCP服务器。
接口函数如下:
- 创建套接字
参数:
domain:有以下参数可选IPv4使用AF_INET
type:服务的类型:TCP为SOCK_STREAM,UDP为SOCK_DRAM
protocol:一般默认为0.
返回值:成功返回文件描述符,失败-1. - 套接字绑定:填充网络
参数:
sockfd:创建的文件描述符(套接字)
addrlen:结构体长度
addr:结构体类型指针,其结构体为(在传参时需强制类型转换):
struct sockaddr_in{
sin_family; //IPv4为AF_INET
sin_port;//端口号
sin_addr;//IP地址
sin_pad;//填充字段
};
套接字监听,是否有other连接自己
参数:
sockfd:套接字文件描述符;
backlog:一般数值不能过大,此处代码设为10.
返回值:成功则将套接字改为了监听套接字- 接收other主机连接,保存连接自己主机的信息
参数:
sockfd:监听套接字
addr:输出型参数,结构体保存客户端套接字信息
addrlen:输入输出型参数,传入与传出结构体大小.
返回值:成功返回一个新的套接字,真正用于服务通信等.- 发起连接请求
参数:
sockfd:套接字文件描述符
addr:连接的服务器socket信息;
addrlen:结构体长度.
以上即为实现TCP服务器的主要函数:代码如下
tcp_server.c:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
//./tcp_server 192.168.x.x 8080
static void usage(const char* proc)
{
printf("usage:%s [server_ip] [server_port]\n",proc);
}
int startup(char* ip,int port)
{
int sock=socket(AF_INET,SOCK_STREAM,0); //创建套接字
if(sock<0)
{
perror("socket");
close(sock);
exit(2);
}
struct sockaddr_in server;
server.sin_family=AF_INET;
server.sin_port=htons(port);
server.sin_addr.s_addr=inet_addr(ip);
if(bind(sock,(struct sockaddr*)&server,sizeof(server))<0) //绑定:填充网络
{
perror("bind");
close(sock);
exit(3);
}
if(listen(sock,10)<0) //监听
{
perror("listen");
close(sock);
exit(4);
}
return sock; //返回监听套接字
}
void* request(void* arg)
{
int new_sock=(int)arg;
while(1)
{
char buf[1024];
ssize_t s=read(new_sock,buf,sizeof(buf)-1);
if(s>0)
{
buf[s]=0;
printf("get new client# %s\n",buf);
write(new_sock,buf,strlen(buf));
}
else if(s==0)
{
printf("client close!!!\n");
break;
}
else
{
perror("read");
break;
}
}
return (void*)0;
}
int main(int argc,char* argv[])
{
if(argc != 3) //命令行用法
{
usage(argv[0]);
return 1;
}
int listen_sock=startup(argv[1],atoi(argv[2])); //创建监听套接字:函数三步
while(1)
{
struct sockaddr_in client;
socklen_t addrlen=sizeof(client);
int new_sock=accept(listen_sock,(struct sockaddr*)&client,&addrlen); //接收客户端信息进行通信
if(new_sock<0)
{
perror("accept");
continue;
}
//version 1.3
pthread_t id;
pthread_create(&id,NULL,request,(void*)new_sock); //主线程监听接收客户端,创建线程为每一个客户端服务
pthread_detach(id); //使线程分离,主线程不用阻塞等待,操作系统去回收
//version 1.2
/*pid_t id=fork(); //即每有一个客户端连接则创建一个子进程为其服务
if(id<0)
{
perror("fork");
close(new_sock);
}
else if(id==0) //child 子进程进行处理请求,服务
{
close(listen_sock);
pid_t _id=fork(); //在子进程再次fork,使子进程的子进程去执行服务
if(_id>0)
{
exit(0); //子进程退出,即让父进程不会阻塞等待
}
else if(_id==0) //child->child
{
while(1)
{
char buf[1024];
ssize_t s=read(new_sock,buf,sizeof(buf)-1);
if(s>0)
{
buf[s]=0;
printf("[%s:%d]# %s\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port),buf);
write(new_sock,buf,strlen(buf));
}
else if(s==0)
{
printf("client close!!!\n");
break;
}
else
{
perror("read");
break;
}
}
}
close(new_sock);
exit(0);
}
else //father 父进程进行监听接收多个客户端
{
close(new_sock);
waitpid(id,NULL,0);
}*/
/*verison 1.1
//服务:
printf("get new client [%s:%d]\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port)); //接收成功
while(1)
{
char buf[1024];
//先从网络上读取客户端请求
ssize_t s=read(new_sock,buf,sizeof(buf)-1);
if(s>0)
{
buf[s]=0;
printf("client# %s\n",buf);
//向客户端服务:写入网络
write(new_sock,buf,strlen(buf));
}
else if(s==0) //若读取为0字节,则客户端关闭断开连接,则不在服务,再次去监听接收
{
printf("client close!!!\n");
break;
}
else
{
perror("read");
break;
}
}*/
}
return 0;
}
tcp_client.c:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
//./tcp_client 192.168.x.x 8080
static void usage(const char* proc)
{
printf("usage:%s [server_ip] [server_port]\n",proc);
}
int main(int argc,char* argv[])
{
if(argc != 3) //命令行用法
{
usage(argv[0]);
return 1;
}
int sock=socket(AF_INET,SOCK_STREAM,0); //创建客户端套接字
if(sock<0)
{
perror("socket");
return 2;
}
struct sockaddr_in server;
server.sin_family=AF_INET;
server.sin_port=htons(atoi(argv[2]));
server.sin_addr.s_addr=inet_addr(argv[1]);
if(connect(sock,(struct sockaddr*)&server,sizeof(server))<0) //客户端与服务器建立连接
{
perror("connect");
return 1;
}
//连接成功
char buf[1024];
while(1)
{
//先往网络上写
printf("Please Enter# ");
fflush(stdout);
ssize_t s=read(0,buf,sizeof(buf)-1);
if(s>0)
{
buf[s-1]=0;
write(sock,buf,strlen(buf));
}
//从网络上读取服务器提供的信息
ssize_t _s=read(sock,buf,sizeof(buf)-1);
if(_s>0)
{
buf[_s]=0;
printf("server$ %s\n",buf);
}
}
//关闭sock,则断开连接
close(sock);
return 0;
}