陈奇网络工作室

TCP和UDP的套接字网络编程

系统操作和维护

前面提到的进程间通信的几种方法:消息信号、管道管道、消息队列msg、共享内存shm、信号量sem。都只适用于一台主机上的进程间通信,那么如何实现两台计算机之间的进程间通信呢?那么,让我们来学习一下远程进程通信。

1远程过程通信

协议层为双方的主机通信进程分配“端口”和缓冲区,方便异地进程间的通信。

1.1TCP/IP协议

以下是OSI参考模型和TCP/IP参考模型之间的对应关系:

TCP/IP协议族

TCP/IP协议组大致分为三个部分:

1.互联网协议(IP)

2.传输控制协议和用户数据报协议

3.一组基于TCP和UDP的专门开发的应用程序。它们包括:TELNET、文件传输协议(FTP)、域名服务(dns)和简单邮件传输程序(SMTP)以及许多其他协议。

应用层协议

用于远程联接服务的标准协议或者实现此协议的软件(可为动词)

文件传输协议(FTP和TFTP)

简单文件传输协议(SMTP)

域名服务(DNS)和其他协议

2网络编程基础

套接字标准扩展到窗口套接字和unix套接字。

linux下的网络编程是通过socket接口实现的。

Socket不仅是一个特殊的IO,也是一个文件描述符。

一个完整的套接字有一个相关的描述(协议;本地地址;本地端口;远程地址;远程端口)。每个套接字都有一个由操作系统分配的本地唯一套接字编号。

2.1插座分类

流式套接字(SOCK_STREAM)

流式套接字可以提供可靠的、面向连接的通信流。它使用TCP协议。TCP保证了数据传输的正确性和顺序性。

数据报套接字(SOCK_DGRAM)

数据报套接字定义了无连接服务。数据通过独立的消息传输,这是无序的,不能保证可靠性和无错性。使用数据报协议UDP协议。

原装插座。

原始套接字允许直接访问IP或ICMP等底层协议,主要用于测试新网络协议的实现。

2.2编程过程

传输控制协议(Transmission Control Protocol)

用户数据报协议(User Datagram Protocol)

特定功能的用法是人。

2.2.1套接字地址结构

关注套接字地址结构:

#包含netinet/in.h

结构足球

{

无符号短sa _ family/*地址族,AF_xxx */

char sa _ data[14];/* 14字节的协议地址*/

};

sa_family的值,一般来说IPV4用的是“AF_INET”。

Sa_data包含了一些远程电脑的地址、端口、插座的数量,里面的数据混在一起。一般我们不使用这种结构,因为我们通常使用的地址是IP端口号。例如:IP192.168.159.2端口3306。这是记录地址的方法。所以一般使用下面的地址结构,知道数据类型是等价的,可以互相转换。

#包含netinet/in.h

struct sockaddr_in {

short int sin _ family/*互联网地址系列*/

无符号短整型sin _ port/*端口号*/

结构in _ addr sin _ addr/*互联网地址*/

无符号字符sin _ zero[8];/*添加0(与struct sockaddr大小相同)*/

};

字节序列转换

因为每台机器中变量的字节存储顺序不同(有的系统是高位在前,低位在后,有的系统是低位在前,高位在后),所以网络传输的数据必须是统一的顺序。因此,有必要为不同于内部字节表示顺序和网络字节顺序的机器转换数据。

htons()——“主机到网络短路”

主机字节顺序转换为网络字节顺序(无符号短整型为2字节)

htonl()—— "主机到网络的长度"

主机字节顺序转换为网络字节顺序(无符号长类型为4字节)

ntohs()—— "网络到主机短路"

网络字节顺序转换为主机字节顺序(无符号短整型为2字节)

ntohl()—— "网络到主机长"

网络字节顺序转换为主机字节顺序(无符号长类型为4字节)

地址格式转换

-linux提供了一个转换函数,将点格式的地址转换为长整数。

inet _地址()能够把一个用数字和点表示互联网协议(互联网协议)地址的字符串转换成一个无符号长整型。

inet_ntoa()能够把网络字节顺序转换为地址结构的数据。

2.2.4基本套接字调用

套接字()绑定()连接()

监听()接受()发送()

接收()发送到()关闭()

recvfrom() close() getsockopt()

setsockopt() getpeername()

getsockname() gethostbyname()

gethostbyaddr() getprotobyname()

fcntl()

练习1-TCP

传输控制协议(传输控制协议)连接,等待客户端输入,将内容发送给服务器,并获取客户端地址。

这里,getsocketname()表示获得本地(自己)的地址;

getpeername()表示获得连接上的客户端的地址(源互联网协议(互联网协议)地址)。

英国铁路公司

server.c

#包含sys/types.h

#包含sys/socket.h

#包含netinet/in.h //sockaddr_in

#包含标准视频

#包含字符串。h

int main()

{

int fd

内部客户端f

内部ret

pid _ t pid

int addrLen=0;

char AC buf[20]=;

struct sockaddr _ in addr={ 0 };//自己的地址

clientAddr={0}中的struct sockaddr _ in//连上的客户端的地址

//1.socket()

fd=socket(PF_INET,SOCK_STREAM,0);

如果(fd==-1)

{

佩罗(插座);

return-1;

}

//2.bind()

地址. sin _ family=AF _ INET

地址。sin _ port=htons(1234);

地址。sin _ addr。s _ addr=inet _ addr(192。168 .159 .6);

ret=bind(fd,(struct sockaddr *)addr,sizeof(struct sockaddr _ in));

if(ret==-1)

{

佩罗(绑定);

return-1;

}

//3.listen()

ret=listen(fd,10);

if(ret==-1)

{

佩罗(听);

return-1;

}

//4.阻塞等待接受()

clientfd=accept(fd,NULL,NULL);

if(clientfd==-1)

{

佩罗(接受);

return-1;

}

//获取客户端地址

addrLen=sizeof(struct sockaddr _ in);

ret=getpeername(clientfd,(struct sockaddr *)clientAddr,addrLen);

if(ret==-1)

{

perror(getpeername);

return-1;

}

printf(客户端登录\\nip: %s,port: %d\\\\n,inet_ntoa(clientAddr.sin_addr),ntohs(client addr。sin _ port));

//5.通信

while(1)

{

memset(acbuf,0,20);

if (read(clientfd,acbuf,20) 0)

{

printf(receive: %s\\\\n,acbuf);

}

}

//6.close()

关闭(FD);

返回0;

}

客户端。c

#包含sys/types.h

#包含sys/socket.h

#包含netinet/in.h //sockaddr_in

#包含标准视频

#包含字符串。h

int main()

{

int fd

内部ret

char AC buf[20]=;

struct sockaddr _ in serAddr={ 0 };

//1 .socket();

fd=socket(PF_INET,SOCK_STREAM,0);

如果(fd==-1)

{

佩罗(插座);

return-1;

}

//2.连接连接()服务器的地址

serAddr.sin _ family=AF _ INET

塞拉德。sin _ port=htons(1234);

塞拉德。sin _ addr。s _ addr=inet _ addr(192。168 .159 .6);

ret=connect(fd,(struct sockaddr *)serAddr,sizeof(struct sockaddr _ in));

if(ret==-1)

{

佩罗(连接);

return-1;

}

//3.通信

while(1)

{

printf(发送:);

fflush(stdout);

scanf(%s,acbuf);

if(strcmp(acbuf,exit)==0)

{

打破;

}

write(fd,acbuf,strlen(AC buf));

}

//4.close()

关闭(FD);

返回0;

}

运行结果:

做个改进,以上代码,只能一个客户端连接上。因为传输控制协议(传输控制协议)是基于点对点的,一个接受()对应一个连接().要想连接多个客户端,就得使用fork(),一个进程用来专门阻塞等待客户端的连接,一个用来处理与已连接上客户端的通信。

代码如下:

server.c

int main()

{

int fd

内部客户端f

内部ret

pid _ t pid

int addrLen=0;

char AC buf[20]=;

char client _ addr[100]=;

struct sockaddr _ in addr={ 0 };//自己的地址

clientAddr={0}中的struct sockaddr _ in//连上的客户端的地址

signal(SIGCHILD,SIG _ IGN);

//1.socket()

fd=socket(PF_INET,SOCK_STREAM,0);

如果(fd==-1)

{

佩罗(插座);

return-1;

}

//会出现没有活动的套接字仍然存在,会禁止绑定端口,出现错误:地址已在使用中。

//由传输控制协议(传输控制协议)套接字时间_等待引起,绑定返回EADDRINUSE,该状态会保留2-4分钟

//if (setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,reuse,sizeof(reuse)) 0)

//{

//perror(setsockopet错误\ \ \ \ n);

//return-1;

//}

//2.bind()

地址. sin _ family=AF _ INET

地址。sin _ port=htons(1234);

地址。sin _ addr。s _ addr=inet _ addr(192。168 .159 .6);

ret=bind(fd,(struct sockaddr *)addr,sizeof(struct sockaddr _ in));

if(ret==-1)

{

佩罗(绑定);

return-1;

}

//3.listen()

ret=listen(fd,10);

if(ret==-1)

{

佩罗(听);

return-1;

}

while(1)

{

//4.阻塞等待接受()

clientfd=accept(fd,NULL,NULL);

if(clientfd==-1)

{

佩罗(接受);

return-1;

}

PID=fork();//父进程负责继续监听等待,子进程父子与已连接客户端通信

如果(pid==-1)

{

佩罗(叉);

return-1;

}

if(pid==0) //子进程

{

//获取客户端地址

addrLen=sizeof(struct sockaddr _ in);

ret=getpeername(clientfd,(struct sockaddr *)clientAddr,addrLen);

if(ret==-1)

{

perror(getpeername);

return-1;

}

sprintf(客户端地址,ip: %s,端口:%d\\\\n,\\

inet_ntoa(clientAddr.sin_addr),ntohs(client addr。sin _ port));

printf(客户登录\\\ n % s \\\ n,client _ addr);

//5.通信

while(1)

{

memset(acbuf,0,20);

if (read(clientfd,acbuf,20)==0) //客户端退出

{

//结束相应的计算机网络服务器进程

close(clientfd);

退出(0);//僵尸进程

}

printf(从% s接收:% s \ \ \ \ n \ \ \ \ n,client_addr,acbuf);

}

}

else //父进程

{

//返回虽然,继续等待

}

}

//6.close()

关闭(FD);

返回0;

}

这里一定要注意,每结束一个客户端,一定要关掉相应的文件描述符,并且结束掉子进程(僵尸进程),不然,随着客户端的增加,进程数会越来越大

客户端。c

int main()

{

int fd

内部ret

内部地址

char AC buf[20]=;

struct sockaddr _ in serAddr={ 0 };

myAddr={0}中的结构sockaddr _ in

//1 .socket();

fd=socket(PF_INET,SOCK_STREAM,0);

如果(fd==-1)

{

佩罗(插座);

return-1;

}

//2.连接连接()服务器的地址

serAddr.sin _ family=AF _ INET

塞拉德。sin _ port=htons(1234);

塞拉德。sin _ addr。s _ addr=inet _ addr(192。168 .159 .6);

ret=connect(fd,(struct sockaddr *)serAddr,sizeof(struct sockaddr _ in));

if(ret==-1)

{

佩罗(连接);

return-1;

}

//获取自己的地址

addrLen=sizeof(struct sockaddr _ in);

ret=getsockname(fd,(struct sockaddr *)myAddr,addrLen);

if(ret==-1)

{

perror(getsockname);

return-1;

}

printf(客户端ip: %s,端口:%d\\\\n,\\

inet_ntoa(myAddr.sin_addr),ntohs(myaddr。sin _ port));

//3.通信

while(1)

{

printf(发送:);

fflush(stdout);

scanf(%s,acbuf);

if(strcmp(acbuf,exit)==0)

{

打破;

}

write(fd,acbuf,strlen(AC buf));

}

//4.close()

关闭(FD);

返回0;

}

运行结果:

练习2-UDP

使用用户数据报协议(用户数据报协议)连接,完成上述内容。但是发现,使用UDP,因为是面向无连接的,所以在没有收到或者发送包之前,是无法得知源互联网协议(互联网协议)地址的。

那用户数据报协议(用户数据报协议)如何知道客户端的互联网协议(互联网协议)地址和端口呢?

1、由客户端显示地高速服务器互联网协议(互联网协议)地址和端口,发消息。

2、隐式的。服务器从收到的包头中得到源互联网协议(互联网协议)和端口号。

server.c

int main()

{

int sockfd

内部ret

char AC buf[20]=;

char client _ addr[100]=;

struct sockaddr _ in addr={ 0 };

clientAddr={0}中的结构sockaddr _ in

int addrLen=sizeof(struct sockaddr _ in);

int reuse=0;

//1.socket()

sockfd=socket(PF_INET,SOCK_DGRAM,0);

if(sockfd==-1)

{

佩罗(插座);

return-1;

}

//2.bind()

地址. sin _ family=AF _ INET

地址。sin _ port=htons(1235);

地址。sin _ addr。s _ addr=inet _ addr(127。0 .0 .1);

ret=bind(sockfd,(struct sockaddr *)addr,addrLen);

if(ret==-1)

{

佩罗(绑定);

return-1;

}

//3.通信

while(1)

{

memset(acbuf,0,20);

if(recvfrom(sockfd,acbuf,100,0,(struct sockaddr *)clientAddr,addrLen)==-1)

{

perror(recvfrom);

return-1;

}

//收到客户端的数据包之后,就可以知道客户端地址

sprintf(客户端地址,ip: %s,端口:%d\\\\n,\\

inet_ntoa(clientAddr.sin_addr),ntohs(client addr。sin _ port));

printf(接收自%s: %s\\\\n,client_addr,acbuf);

}

//4 .关闭

关闭(sockfd);

返回0;

}

客户端。c

int main()

{

int sockfd

内部ret

int addrLen=sizeof(struct sockaddr _ in);

char AC buf[20]=;

struct sockaddr _ in serAddr={ 0 };

myAddr={0}中的结构sockaddr _ in

//1.socket()

sockfd=socket(PF_INET,SOCK_DGRAM,0);

if(sockfd==-1)

{

佩罗(插座);

return-1;

}

serAddr.sin _ family=AF _ INET

塞拉德。sin _ port=htons(1235);

塞拉德。sin _ addr。s _ addr=inet _ addr(127。0 .0 .1);

//2.通信

while(1)

{

printf(发送:);

fflush(stdout);

scanf(%s,acbuf);

if(strcmp(acbuf,exit)==0)

{

打破;

}

sendto(sockfd,acbuf,20,0,(struct sockaddr *)serAddr,addrLen);

//获取自己的地址

ret=getsockname(sockfd,(struct sockaddr *)myAddr,addrLen);

if(ret==-1)

{

perror(getsockname);

return-1;

}

printf(客户端ip: %s,端口:%d\\\\n\\\\n,\\

inet_ntoa(myAddr.sin_addr),ntohs(myaddr。sin _ port));

}

//3 .关闭

关闭(sockfd);

返回0;

}

运行结果:

会发现,此时是可以直接运行多个客户端的,因为,UDP是面向无连接的,可以是一对多,多对一,多对多的,只要客户端知道服务器地址,就可以连上。

英国铁路公司

Ps:本人理解有限,还未学习完,有错请指出。

更多关于云服务器,域名注册,虚拟主机的问题,请访问西部数码代理商官网:www.chenqinet.cn

后台-系统设置-扩展变量-手机广告位-内容页底部广告位3