您的位置:首页 > 理论基础 > 计算机网络

Unix网络编程---第四次作业

2015-11-26 15:32 609 查看
Unix网络编程---第四次作业

要求:

服务器利用I/O复用技术,实现同时向多个客户提供服务。

服务端:

接收客户连接请求,并打印客户IP地址及端口号,然后接收客户发来的字符串,并打印该字符串和其来自与哪个客户。同时向客户返回该字符串。

客户端:

从命令行接收服务器地址,并向服务器发起连接请求,连接成功后,从标准输入接收字符串并发送给服务器,等待服务器响应并打印接收的信息

思路:

针对本次作业,即通过select设置需要等待的描述符。首先将监听套接字描述符在描述符集中打开,当有新的客户请求连接时,select返回,监听套接字可读,于是建立连接,当连接建立以后,产生新的已连接套接字描述符,将其在描述符集中打开,此后每次select返回,检查哪些套接字描述符可用。

针对本题,我没有做题目中的要求功能,而是相对简单类似的,通过用I/O复用解决第三个作业中存在当服务端被kill后,客户端无法得知的问题。

程序实现:

服务器端:my_server4.c

#include <sys/socket.h>
#include <sys/types.h>/*The funcion sizeof,socklen_t need*/
#include <netinet/in.h>/*The funcion sockaddr_in need*/
#include <unistd.h>
#include <arpa/inet.h>/*The funcion inet_ntoa need*/
#include <string.h>/*The funcion strlen need*/
#include <errno.h>/*errno == EINTR*/
#include <sys/wait.h>/*WNOHANG*/
#include <pthread.h>

#define  UPORT 8088 /*This is the port number used by me */
#define  MAXLINE 255
#define  LISTENQ 32
#define  NAMELEN 21
typedef struct {
char buf[MAXLINE+1];
ssize_t n;
int sockfd;
char name[NAMELEN+1];
} readline;

pthread_key_t ser_key;
pthread_once_t ser_once=PTHREAD_ONCE_INIT;

void str_echo( readline *tsd);
void sig_chld(int signo);
void ser_destructor(void *ptr);
void service_once(void);
static void *doit(void *arg);
void echo_name(readline *tsd);

int main(int argc, char **argv)
{
int    listenfd ,connfd, reuse=1;//if the value of reuse is not zero, mean open this reuse address selection, or else ban this function
int *cfdp;
struct sockaddr_in    servaddr, cliaddr;
socklen_t  clilen;
pthread_t tid,tid1;
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(UPORT);    /* daytime server */
if( setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse))==-1){
perror("There is an error occured when the program set REUSEADDR symbol\n");
return -1;
}
if(bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr))==-1){
perror("%s\r\n","bind error");
exit(-1);
}
listen(listenfd, LISTENQ);
signal(SIGCHLD, sig_chld);
for ( ; ; ) {
clilen=sizeof(cliaddr);
cfdp=(int *)malloc(sizeof(int));
if((*cfdp = accept(listenfd, (struct sockaddr *) &cliaddr, &clilen))==-1){
perror("%s\r\n","An error occured while tring to creat a connfd! ");
exit(-1);
}
printf("the new connection address is:%s:%d\r\n",inet_ntoa(cliaddr.sin_addr),cliaddr.sin_port);
if(pthread_create(&tid, NULL, &doit, cfdp )!=0){
perror("pthread_create: error\n");
exit(-1);
}
}
}
void echo_name(readline *tsd){
char  tmp;
int i, j;
char name[21];//all
strcpy(tsd->buf,"Dear client please input your name: ");
if(write(tsd->sockfd, tsd->buf,strlen(tsd->buf)+1)==-1) {
perror("write error");
exit(-1);
}

if ((tsd->n=read(tsd->sockfd,tsd->name, NAMELEN)) > 0) { /*tsd->=*tsd.n*/
tsd->name[tsd->n-1]=0;/*change '\n' to 0*/
printf("the client's name: [ %s ]\n", tsd->name);
strcpy(tsd->buf,"Now,you can begin to input the string you need to conver!\n");
if(write(tsd->sockfd, tsd->buf,strlen(tsd->buf)+1)==-1) {
perror("write error");
exit(-1);
}
}
if (tsd->n<0 && errno == EINTR) {
perror("read:error interrupt");
}
else if (tsd->n<0) {
perror("str_echo:read error");
exit(-1);
}
}
void str_echo( readline *tsd) {
char  tmp;
int i, j;
again:
while ( (tsd->n=read(tsd->sockfd,tsd->buf, MAXLINE)) > 0) { /*tsd->=*tsd.n*/
tsd->buf[tsd->n]=0;
printf("client [ %s ] input string:%s",tsd->name,tsd->buf);
for(i=0, j=tsd->n-2; i<j; i++, j--) {
tmp=tsd->buf[i];
tsd->buf[i]=tsd->buf[j];
tsd->buf[j]=tmp;
}
if(write(tsd->sockfd, tsd->buf, tsd->n)==-1) {
perror("write error");
exit(-1);
}
printf("inverted order %s's string:%s",tsd->name,tsd->buf);
}
if (tsd->n<0 && errno == EINTR) {
goto again;
}
else if (tsd->n<0) {
perror("str_echo:read error");
exit(-1);
}
}
void sig_chld(int signo)
{
pid_t pid;
int stat;
while( (pid = waitpid(-1,&stat,WNOHANG))>0)
printf("child %d terminated\n", pid);
return;
}
void ser_destructor(void *ptr) {
free(ptr);
printf("one of the tsd end:%d\n",pthread_self());
}
void service_once(void) {
pthread_key_create(&ser_key, ser_destructor);
}

static void *doit(void *arg) {
readline *tsd;
if(pthread_detach(pthread_self())!=0) {
perror("pthread_detach:error\n");
exit(-1);
}
pthread_once(&ser_once,service_once);
if( (tsd=pthread_getspecific(ser_key)) == NULL){
tsd=calloc(1,sizeof(readline));
pthread_setspecific(ser_key,tsd);
tsd->sockfd=*( (int*)arg);
}
//printf("%d\n",tsd->sockfd);
echo_name(tsd);
str_echo( tsd);
if(close(*( (int*)arg))==-1){
perror("close:error\n");
exit(-1);
}
pthread_exit(0);
return;
}


客户端:my_client4.c

#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/select.h>
#include <sys/time.h> /*there is the time struct in the select variate*/

#define  UPORT 8088 /*This is the port number used by me */
#define  MAXLINE 255

void str_cli(FILE *fp, int sockfd) {
char recvline[MAXLINE],sendline[MAXLINE],buf[MAXLINE+1];/*recv or send use a buffer array*/
fd_set rset;
int maxfdp,n;
int    stdineof=0;
FD_ZERO(&rset);
for(;;){
if(stdineof==0){
FD_SET(fileno(fp),&rset);
}
FD_SET(sockfd,&rset);
maxfdp=(fileno(fp)>sockfd)?fileno(fp):sockfd+1;
if(select(maxfdp, &rset, NULL, NULL, NULL)==-1) {
perror("select error\n");
exit(-1);
}
if(FD_ISSET(sockfd,&rset)){
if( (n=read(sockfd, recvline, MAXLINE)) <= 0 ) {
if(stdineof==1){
printf("you have terminated the conection with server!\n");
return;/*normal termination*/
}
else{
printf("server terminated prematurely!\n");
exit(0);
}
}
write(fileno(stdout),recvline,n);
}
if(FD_ISSET(fileno(fp),&rset)){
memset(buf,0,sizeof(buf));
if( (n=read(fileno(fp), sendline, MAXLINE))<= 0 ) {
stdineof=1;
shutdown(sockfd,SHUT_WR);/*send FIN*/
FD_CLR(fileno(fp),&rset);
continue;
}
sendline[strlen(sendline)]='\0';
if(write(sockfd, sendline, n)==-1){
perror("write sockfd:error!\n");
exit(-1);
}
//memset(buf,0,sizeof(buf));
}
}
}

int main(int argc, char **argv)
{
int                    sockfd, n;
struct sockaddr_in    servaddr;

if (argc != 2){
perror("usage: a.out <IPaddress>");
exit(-1);
}

if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
perror("socket error");
exit(-1);
}

bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port   = htons(UPORT);    /* daytime server */
if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0){
printf("inet_pton error for %s", argv[1]);
exit(-1);
}

if (connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0){
perror("connect error");
exit(-1);
}
str_cli(stdin,sockfd); /*do it all*/

exit(0);
}


运行截图:

编译:gcc -pthread my_client4.c -o my_client4

Gcc -pthread my_server4.c -o my_server4

服务端运行:./my_server3

客户端1运行:./my_client3 192.168.1.128

客户端2运行:./my_client3 192.168.1.128

本实验是在两台虚拟机上操作

服务端ip:192.168.1.128

客户端1 ip:192.168.1.119

客户端2 ip:192.168.1.128

服务端实现:利用线程专用数据TSD,并设置析构函数ser_destructor当线程退出时调用。通过listen套接字,为每个客户创建一个线程,并通过线程专用数据存储客户名与套接字描述符。

客户端实现:利用I/O复用,设置需要等待的描述符集,标准输入描述符与socket描述符,为每个需要监听的动作设置相应的打开位,当select返回时检测是哪一个描述符可读,于是进行相应的操作。

1、服务端:





2、客户端:





总结:

1、 本作业的服务端相对于作业三,稍有改动。由于在作业三中客户端是利用fets来循环获取标准输入的字符串,并在读入后在最后一个位置加上\0的字符串结束符,所以当写给服务端时,服务端不必给字符串加结束符。但是由于在本次作业中客户端直接从stdin中读数据,然后写给服务端,所以在服务端处理时需要加字符串结束符\0,以免多于的字符返回回来,或者说处理的时候出错。

2、 通过n来记录read, write所操作的字节数,来确定字符串边界。

3、 注意strlen,与sizeof区别,前者为字符串长度,后者为内存大小,数组中即为数组的长度。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: