1.基本概念

1)网络模型

2)基本协议

第一部分称为网络层。主要包括Internet 协议(IP)、网际控制报文协议(ICMP)和地址解析协议(ARP) Internet 协议(IP)。

网际控制报文协议(ICMP):允许网际路由器传输差错信息或测试报文。

地址解析协议(ARP):它处于IP和数据链路层之间,根据ip地址找到mac地址。

IP主要功能:数据传送 ,寻址 ,路由选择,数据报文的分段

第二部分是传输层协议,包括传输控制协议和用户数据报文协议

传输控制协议(TCP): 该协议对建立网络上用户进程之间的对话负责,它确保进程之间的可靠通信。

UDP 提供不可靠的非连接型传输层服务,它允许在源和目的地之间传送数据,而不必在传送数据之前建立对话。它主要用于那些非连接型的应用程序,如:视频点播

第三部分是应用协议层,这部分主要包括Telnet,文件传送协议(FTP 和TFTP),简单文件传送协议(SMTP)和域名服务(DNS)等协议。

2.Socket

Linux中的网络编程通过Socket(套接字)接口实现,Socket是一种文件描述符.

1)sockaddr和sockaddr_in结构:

sockaddr结构:

struct sockaddr

 {
	    u_short sa_family;  // 地址族,采用“AF_xxx”的形式,如:AF_INET  

		char sa_data[14];  //     14字节的特定协议地址
 };
  
   

编程中一般并不直接针对sockaddr数据结构操作,而是使用与sockaddr等价的sockaddr_in数据结构.

struct sockaddr_in
    {
       short int sin_family;  /* Internet地址族 */

       unsigned short int sin_port;  /* 端口号 */

       struct in_addr sin_addr;   /* IP地址 */

       unsigned char sin_zero[8];  /* 填0 */
     };


struct in_addr
   {
	    unsigned long s_addr;   // S_addr: 32位的地址
   }
 


 2)ip地址点分制和数字转换

IP地址通常由数字加点(192.168.0.1)的形式表示,而在struct in_addr中使用的是IP地址是由32位的整数表示

的,为了转换我们可以使用下面两个函数:

int inet_aton(const char *cp,struct in_addr *inp)表示将a.b.c.d形式的IP转换为32位的IP,存储在
 
inp指针里面。


char *inet_ntoa(struct in_addr in) //是将32位IP转换为a.b.c.d的格式

3)字节序转换(网络中采用大端排序)

不同类型的 CPU 对变量的字节存储顺序可能不同:有的系统是高位在前,低位在后,而有的系统是低位在前,高位在后,而网络传输的数据顺序是一定要统一的。所以当内部字节存储顺序和网络字节顺序不同时,就一定要进行转换.网络字节顺序采用big endian排序方式.

#include <arpa/inet.h>

	     uint32_t htonl(uint32_t hostlong);

		 uint16_t htons(uint16_t hostshort);

		 uint32_t ntohl(uint32_t netlong);

         uint16_t ntohs(uint16_t netshort);

h表示host,n表示network,l表示32位长整数,s表示16位短整数。例如htonl表示将32位的长整数从主机字节

序转换为网络字节序,例如将IP地址转换后准备发送。如果主机是小端字节序,这些函数将参数做相应的大小端转

换然后返回,如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回

htons
    把unsigned short类型从主机序转换到网络序
htonl
    把unsigned long类型从主机序转换到网络序
ntohs
    把unsigned short类型从网络序转换到主机序
ntohl
    把unsigned long类型从网络序转换到主机序

 4)传输模型

服务器:

1. 创建一个socket,用函数socket()

2. 绑定IP地址、端口等信息到socket上,用函数bind()

3.设置允许的最大连接数,用函数listen()

4.接收客户端上来的连接,用函数accept()

5.收发数据,用函数send()和recv(),或者read()和write()

6.关闭网络连接

客户端:

1.创建一个socket,用函数socket()

2.设置要连接的对方的IP地址和端口等属性

3.连接服务器,用函数connect()

4.收发数据,用函数send()和recv(),或者     read()和write()

5.关闭网络连接

6.函数

int socket(int family, int type, int protocol);成功返回头接口,如果socket()调用出错则返回-1


对于IPv4,family参数指定为AF_INET

对于TCP协议,type参数指定SOCK_STREAM,表示面向流的传输协议

如果是UDP协议,则type参数指定为SOCK_DGRAM,表示面向数据报的传输协议

protocol参数的介绍从略,指定为0即可




int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen); bind()成功返回0,失

败返回-1。

bind()的作用是将参数sockfd和myaddr绑定在一起,使sockfd这个用于网络通讯的文件描述符监听myaddr所

描述的地址和端口号。服务器需要调用bind绑定一个固定的网络地址和端口号。、



在server代码的socket()和bind()调用之间插入如下代码:

int opt = 1; 
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));//重复绑定






sockaddr_in 初始化:

bzero(&servaddr, sizeof(servaddr));//首先将整个结构体清零

servaddr.sin_family = AF_INET; //设置地址类型

servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //网络地址,为INADDR_ANY,这个宏表示本地的任意IP

地址,因为服务器可能有多个网卡,每个网卡也可能绑定多个IP地址,这样设置可以在所有的IP地址上监听,直到

不某个客户端建立了连接时才确定下来到底用哪个IP地址

servaddr.sin_port = htons(SERV_PORT);//端口号为SERV_PORT,我们定义为8000 

int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen); 

服务器调用accept()接受连接,如果服务器调用accept()时还没有客户端的连接请求,就阻塞等待直到有客户端

连接上来.

cliaddr是一个传出参数,accept()返回时传出客户端的地址和端口号.

addrlen参数是一个传入传出参数(value-result argument),传入的是调用者提供的缓冲区cliaddr的长度以

避免缓冲区溢出问题,传出的是客户端地址结构体的实际长度(有可能没有占满调用者提供的缓冲区)。

如果给cliaddr参数传NULL,表示不关心客户端的地址


accept函数返回值成功时返回非负值,失败时返回-1

int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen); 

客户端需要调用connect()连接服务器,connect和bind的参数形式一致,区别在于bind的参数是自己的地址,而

connect的参数是对方的地址.

accept函数的第一个参数为服务器的socket描述字,第二个参数为指向struct sockaddr *的指针,用于返回客

户端的协议地址,第三个参数为协议地址的长度。

如果accpet成功,(!= -1)那么其返回值是由内核自动生成的一个全新的描述字,代表与返回客户的TCP连接。


一般情况下:
send(),recv()用于TCP,sendto()及recvfrom()用于UDP

但是send(),recv()也可以用于UDP,sendto()及recvfrom()也可以用于TCP

sendto可以在参数中指定发送的目标地址 , send需要socket已建立连接, sendto 可用于无连接的 socket对于

send的有连接socket,两者一样,sendto最后两个参数没用.


send函数:
int send( SOCKET s , const char FAR *buf , int len , int flags );  
不论是客户还是服务器应用程序都用send函数来向TCP连接的另一端发送数据。
客户程序一般用send函数向服务器发送请求,而服务器则通常用send函数来向客户程序发送应答。
第一个参数指定发送端套接字描述符;
第二个参数指明一个存放应用程序要发送数据的缓冲区;
第三个参数指明实际要发送的数据的字节数;
第四个参数一般设置为0。

如果send函数copy数据成功,就返回实际copy的字节数,如果send在copy数据时出现错误,那么send就返回SOCKET_ERROR;如果send在等待协议传送数据时网络断开的话,那么send函数也返回SOCKET_ERROR。



recv函数
int recv( SOCKET s , char FAR * buf , int len , int flags );   
不论是客户还是服务器应用程序都用recv函数从TCP连接的另一端接收数据。
该函数的第一个参数指定接收端套接字描述符;
第二个参数指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据;
第三个参数指明buf的长度;
第四个参数一般置0。

recv函数返回其实际copy的字节数。如果recv在copy时出错,那么它返回SOCKET_ERROR;如果recv函数在等待协议接收数据时网络中断了,那么它返回0。






在无连接的数据报socket方式下,由于本地socket并没有与远端机器建立连接,所以在发送数据时应指明目的地址,sendto() 函数原型为:  
   int sendto(int sockfd, const void *msg,int len , unsigned int flags, const struct sockaddr *to, int tolen); 

该函数比 send() 函数多了两个参数,to表示目地机的IP地址和端口号信息,而tolen常常被赋值为sizeof 

(struct sockaddr)。sendto 函数也返回实际发送的数据字节长度或在出现发送错误时返回-1。 



int recvfrom(int sockfd,void *buf,int len,unsigned int lags,struct sockaddr *from,int *fromlen);
 
from是一个struct sockaddr类型的变量,该变量保存源机的IP地址及端口号。fromlen常置为sizeof 

(struct sockaddr)。当recvfrom() 返回时,fromlen 包含实际存入from中的数据字节数。recvfrom() 

函数返回接收到的字节数或当出现错误时返回-1,并置相应的errno。 

7.实例TCP传输

客户端:

#include<stdio.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h> 
#include <arpa/inet.h>

#define server_port 8000
int main(int argc,char **argv)
{
		int socketfd;
		char buf[50];
		int n;
		struct sockaddr_in client_addr,server_addr;
		socklen_t addr_len;
		char sendbuf[50];
		char recvbuf[50];

		int c;
		if(argc != 2)
		{
				printf("input the ip!\n");
				exit(-1);
		}
		socketfd=socket(AF_INET,SOCK_STREAM,0);

		bzero(&server_addr,sizeof(server_addr));
		server_addr.sin_family=AF_INET;
		server_addr.sin_addr.s_addr=inet_addr(argv[1]);
		server_addr.sin_port=htons(server_port);
        
		addr_len=sizeof(server_addr);
		c=connect(socketfd,(struct sockaddr *)&server_addr,sizeof(server_addr));

		if(c == 0)
		{
				printf("success\n");
		}
		else
		{
				perror("fail\n");
		}
	
		fgets(sendbuf,50,stdin);

		printf("%s\n",sendbuf);

	sendto(socketfd,sendbuf,strlen(sendbuf),0,(struct sockaddr *)&server_addr,addr_len);
    
		n=recvfrom(socketfd,recvbuf,50,0,(struct sockaddr *)&server_addr,&addr_len);

	recvbuf[n]='\0';

        fputs(recvbuf,stdout);


		close(socketfd);
		return 0;


}

服务器:

#include<stdio.h>
#include<sys/socket.h>
#include<string.h>
#include<netinet/in.h>
#include<unistd.h>
#define server_port 8000
int main()
{
		int listenfd,connfd;
		char msg[50];
		socklen_t addrlen;
		struct sockaddr_in server_addr,client_addr;
		int n;

		listenfd=socket(AF_INET,SOCK_STREAM,0);

		bzero(&server_addr,sizeof(server_addr));
        server_addr.sin_family=AF_INET;
		server_addr.sin_addr.s_addr=htonl(INADDR_ANY);
		server_addr.sin_port=htons(server_port);


		addrlen=sizeof(server_addr);
		bind(listenfd,(struct sockaddr *)&server_addr,addrlen);

		listen(listenfd,999);
		printf("listening........\n");

		addrlen=sizeof(client_addr);
		connfd=accept(listenfd,(struct sockaddr *)&client_addr,&addrlen);
        
		if(connfd != -1)
		{
				printf("success\n");
		}
		else
		
		{
				perror("fail\n");
		}

		n=recvfrom(connfd,msg,50,0,(struct sockaddr *)&client_addr,&addrlen);
         
	    msg[n]='\0'; 
		printf("the recv msg is %s\n",msg);

		sendto(connfd,msg,strlen(msg),0,(struct sockaddr *)&client_addr,addrlen);

		close(connfd);

		close(listenfd);
		return 0;



}

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐