版本一:
客户端和服务器端可以进行通信,但是只能有一个客户端。
1.服务器端程序tcp_server.c
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
static void Usage(const char *proc)
{//提示客户端运行时的形式ip+端口号
printf("%s[local_ip][local_port]\n",proc);
}
int startup(const char *_ip,int _port)
{
//建立服务器端套接字socket
int sock=socket(AF_INET,SOCK_STREAM,0);
if(sock<0)
{
perror("socket");
exit(2);
}
struct sockaddr_in local;
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)
{
perror("bind");
exit(3);
}
//建立监听队列
if(listen(sock,5)<0)
{
perror("listen");
exit(4);
}
return sock;
}
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 len=sizeof(client);
//等待客户端连接请求到达
int new_fd=accept(listen_sock,(struct sockaddr*)&client,&len);
if(new_fd<0)
{
perror("accept");
continue;
}
//客户端连接成功
printf("get a new client,%s:%d\n",inet_ntoa(client.sin_addr),ntohs(\
client.sin_port));
while(1)
{
char buf[1024];
//接收客户端发送的数据
ssize_t s=read(new_fd,buf,sizeof(buf)-1);
if(s>0)
{
buf[s]=0;
printf("client: %s\n",buf);
write(new_fd,buf,sizeof(buf)-1);
}
else
{//客户端的数据被读完,即连接断开
printf("read done...,break\n");
break;
}
}
}
}
2.客户端程序 tcp_client.c
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
static void Usage(const char *proc)
{//提示客户端运行时的形式ip+端口号
printf("%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 remote;
remote.sin_family=AF_INET;
remote.sin_port=htons(atoi(argv[2]));
remote.sin_addr.s_addr=inet_addr(argv[1]);
//与远程服务器连接
if(connect(sock,(struct sockaddr*)&remote,sizeof(remote))<0)
{
perror("connect");
return 3;
}
while(1)
{
char buf[1024];
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 echo#%s\n",buf);
}
}
}
}
①先运行服务器端,当客户端连接上服务器端时,服务器端会给出提示:
②客户端向服务器端发送数据,若服务器端接收到数据时,会回显在客户端的屏幕上
③服务器端接收到客户端的数据,当客户端断开连接时,服务器端显示数据已读完
版本二:
多进程服务器:
修改服务器,让服务器端可以被多个客户端连接,实现多用户、多进程
tcp_server.c
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
static void Usage(const char *proc)
{
printf("%s[local_ip][local_port]\n",proc);
}
int startup(const char *_ip,int _port)
{
int sock=socket(AF_INET,SOCK_STREAM,0);
if(sock<0)
{
perror("socket");
exit(2);
}
struct sockaddr_in local;
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)
{
perror("bind");
exit(3);
}
if(listen(sock,5)<0)
{
perror("listen");
exit(4);
}
return sock;
}
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 len=sizeof(client);
int new_fd=accept(listen_sock,(struct sockaddr*)&client,&len);
if(new_fd<0)
{
perror("accept");
continue;
}
printf("get a new client,%s:%d\n",inet_ntoa(client.sin_addr),ntohs(\
client.sin_port));
//创建子进程
pid_t id=fork();
if(id<0)
{
perror("fork");
close(new_fd);
}
else if(id==0)
{//child
close(listen_sock);
if(fork()>0)
{//子进程退出,子进程的子进程变为孤儿进程,被1号进程回收,让子进程的子进程继续运行程序,父进程不需要再回收子进程
exit(0);
}
while(1)
{
char buf[1024];
ssize_t s=read(new_fd,buf,sizeof(buf)-1);
if(s>0)
{
buf[s]=0;
printf("client: %s\n",buf);
write(new_fd,buf,sizeof(buf)-1);
}
else
{
printf("read done...,break\n");
return;
}
}
close(new_fd);
}
else
{//father
close(new_fd);
}
}
}
版本三:
多线程服务器:
编译时需要链接线程的库 -lpthread
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
static void Usage(const char *proc)
{//提示客户端运行时的形式ip+端口号
printf("%s[local_ip][local_port]\n",proc);
}
int startup(const char *_ip,int _port)
{
//建立服务器套接字
int sock=socket(AF_INET,SOCK_STREAM,0);
if(sock<0)
{
perror("socket");
exit(2);
}
struct sockaddr_in local;
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)
{
perror("bind");
exit(3);
}
//建立监听队列
if(listen(sock,5)<0)
{
perror("listen");
exit(4);
}
return sock;
}
void *handlerRequest(void *arg)
{//处理客户端数据
int new_fd=(int)arg;
while(1)
{
char buf[1024];
ssize_t s=read(new_fd,buf,sizeof(buf)-1);
if(s>0)
{
buf[s]=0;
printf("client: %s\n",buf);
write(new_fd,buf,sizeof(buf)-1);
}
else
{
printf("read done...,break\n");
break;
}
}
}
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 len=sizeof(client);
//等待客户端的请求连接
int new_fd=accept(listen_sock,(struct sockaddr*)&client,&len);
if(new_fd<0)
{
perror("accept");
continue;
}
printf("get a new client,%s:%d\n",inet_ntoa(client.sin_addr),ntohs(\
client.sin_port));
pthread_t id;
//创建线程
pthread_create(&id,NULL,handlerRequest,(void*)new_fd);
//线程运行结束后释放资源
pthread_detach(id);
}
}
bind错误:
当服务器和客户端连接成功后,服务器不小心按下ctrl-c退出后,再次连接时,会显示一个bind错误,bind:Address already in use。
原因: 该错误是由于TCP 套接字状态 TIME_WAIT 引起,TIME_WAIT状态在套接字关闭后约保留 2 到 4 分钟。当TIME_WAIT 状态退出之后,套接字被删除,此时该地址才能被重新绑定而不出问题。
解决方法:①等待2~4分钟重新连接;
②给套接字应用 SO_REUSEADDR 套接字选项,以便端口可以马上重用
③换一个端口使用;
如图所示: