Liunx系统编程之网络编程——socket

TCP/UDP对比

1.TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据前,不需要建立连接
2.TCP提供可靠的服务,也就是说通过TCP连接传送的数据是无差错,不丢失,不重复且按序到达的;UDP是尽最大努力交付,即保证可靠交付。
3.TCP是面向字节流,实际上是TCP把数据看成是一连串无结构的字节流;UDP是面向报文的,UDP没有拥塞控制,因此网络出现拥塞不会是源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议…)
4.每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信
5.TCP的首部开销20字节;UDP的首部开销小,只有8个字节
6.TCP是逻辑通信信道是全双工的可靠信道;UDP是不可靠信道

端口号作用

一台拥有IP地址的主机可以提供许多服务,比如Web服务、FTP服务、SMTP服务等
这些服务完全可以通过I个IP地址来实现。那么。主机是怎样区分不同的网络服务呢?显然不能只靠P地址,因为IP地址与网络服务的关系是一对多的关系。
实际上是通过IP地址+端口号"来区分不同的服务的。端口提供了—种访向通道,服务器―般都是通过知名端口号来识别的。例如,对于每个TCP/IP实现来说,FTP服务器的TCP端口号都是21,每个Telnet服务器的TCP端口号都是23,每个TFTP(简单文件传送协议)服务器的UDP端口号都是69

字节序

字节序,即字节在电脑中存放时的序列与输入(输出)时的序列是先到的在前还是后到的在前。
1. Little endian:将低序字节存储在起始地址
2. Big endian:将高序字节存储在起始地址
网络字节序=大端字节序
Liunx系统编程之网络编程——socket

socket服务器和客户端的开发步骤

Liunx系统编程之网络编程——socket

上方这幅图可概括为:
1.创建套接字
2.为套接字添加信息(IP地址和端口号)
3.监听网络连接
3.监听到有客户端接入,接受一个连接
5.进行数据交互
6.关闭套接字,断开连接

socket函数的介绍

1.socket()

头文件:

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>			//man手册可查 

函数原型:

int socket(int domain, int type, int protocol);
int domain:指明所使用的协议族,通常为AF_INET,表示互联网协议族(TCP/IP协议族)
       AF_UNIX, AF_LOCAL   Unix域
       AF_INET             IPv4 因特网域
       AF_INET6            IPv6 因特网域
       AF_ROUTE 		   路由套接字
       AF_KEY			   密钥套接字
       AF_UNSPEC		   未指定

int type:指定socket的类型
			SOCK_STREAM:流式套接字提供的可靠的,面向连接的通信流;它使用TCP协议,从而保证了数据传输的正确性和顺序性
			SOCK_DGRAM:数据报套接字定义了一种无连接的服务,数据报通过相互独立的报文进行传输,是无序的,并且不保证是可靠,无差错的。它使用的数据报协议是UDP
			SOCK_RAW:允许程序使用底层协议,原始套接字允许对底层协议如IP或ICMP进行直接访问,功能强大但是使用较为不方便,主要用于一些协议的开发。

int protocol:通常赋值为0;
				0:表示自动选择type类型对应的默认协议
				IPPROTO_TCP    TCP传输协议
				IPPROTO_UDP    UDP传输协议
				IPPROTO_SCTP   SCTP传输协议
				IPPROTO_TICP   TICP传输协议

2.bind()

功能:用于绑定IP地址和端口号到 socketfd

头文件:

  #include <sys/types.h>          /* See NOTES */
   #include <sys/socket.h>

函数原型:

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
int sockfd:是一个socket描述符

const struct sockaddr *addr:是一个指向包含有本机IP地址及端口号等信息的sockaddr类型的结构体指针,指向要绑定给sockfd的协议地址结构,这个地址结构根据地址创建socket时的地址协议族的不同而不同
struct sockaddr {
               sa_family_t sa_family;//协议族
               char        sa_data[14];//IP+端口号
}//可同等替换为下面这个
struct sockaddr_in {//如何找到这个结构体,在下方有详解
  					__kernel_sa_family_t  sin_family; //协议族
 					__be16                sin_port; //端口号     
  					struct in_addr        sin_addr;//IP地址结构体 ,   
 					unsigned char         __pad[__SOCK_SIZE__ - sizeof(short int) -sizeof(unsigned short int) - sizeof(struct in_addr)];/*填充 没有实际意义,只是为跟sockaddr结构在内存在内存中对其,这样两者才能相互*/
};

socklen_t addrlen:数据大小(sizeof(struct sockaddr_in))

如何找到到struct sockaddr_in这个结构体和struct in_addr sin_addr这个结构体
1.cd /usr/include/
2.grep “struct sockaddr_in {” * -nir (n表示显示行号,i表示不区分大小写)
Liunx系统编程之网络编程——socket
Liunx系统编程之网络编程——socket
找他struct in_addr sin_addr这个结构体用同样的方法
Liunx系统编程之网络编程——socket
Liunx系统编程之网络编程——socket

3.地址转换API

int inet_aton(const char *cp, struct in_addr *inp);
把字符串形式的IP地址如"192.168.1.123"装换为网络能识别的格式
const char *cp:为你的IP地址
struct in_addr *inp:存放你这个IP底盘指针结构体(在上面bind()中有这个结构体),例如:&s_addr.sin_addr
 char *inet_ntoa(struct in_addr in);
把网络格式的IP地址转换成字符串形式
ex:inet_ntoa(c_addr.sin_addr)

4.listen()

功能:监听设置函数,只能用于服务端,为了接受连接,先用socket()创建一个套接口的描述字,然后用listen()创建套接口并为申请进入的连接建立一个后备日志,然后便可用accept()接受连接了。listen()仅适用于支持连接的套接口,如SOCK_STREAM类型的。套接口s处于一种“变动”模式,申请进入的连接请求被确认,并排队等待被接受。这个函数特别适用于同时有多个连接请求的服务器;如果当一个连接请求到来时,队列已满,那么客户将收到一个WSAECONNREFUSED错误。

当没有可用的描述字时,listen()函数仍试图正常地工作。它仍接受请求直至队列变空。当有可用描述字时,后续的一次listen()或accept()调用会将队列按照当前或最近的“后备日志”重新填充,如有可能的话,将恢复监听申请进入的连接请求。

函数原型:
int listen(int sockfd, int backlog);
int sockfd:是一个socket描述符
int backlog:等待连接队列的最大长度。

5.accept()

功能:accept函数由 TCP服务器调用,用于从已完成连接队列队头返回下一个已完成连接。如果已完成连接队列为空,那么进程被投入睡眠。

函数原型:

 int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
int sockfd:是一个socket描述符
struct sockaddr *addr:用来返回已连接的对端(客户端)的协议地址,也就是客户端地址,(struct sockaddr *)&c_addr
socklen_t *addrlen:客户端地址长度, int clen = sizeof(struct sockaddr_in);

6.connect()

功能:客户端连接主机
函数原型:

 int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
int sockfd:目的服务器的文件描述符
const struct sockaddr *addr:是服务器端的IP地址和端口号的地址结构指针,(struct sockaddr *)&c_addr
socklen_t addrlen:地址长度,常被设置为sizeof(struct sockaddr_in);

//成功返回0 失败返回-1 并且error中包含相应的错误码 

Liunx系统编程之网络编程——socket

7.字节序转换API

 #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代表net,s代表short(两个字节),l代表long(四个字节),通过上面四个函数可以实现主机和网络字节序的之间的转换。有时可以用INADDR_ANY,INADDR_ANY指定地址让操作系统之间获取*/

代码示例:

服务端代码 sever.c

#include<stdio.h>
#include<stdlib.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
//#include<linux/in.h> 与netinet/in.h冲突:了
#include <arpa/inet.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include<string.h>

int main(int argc, char **argv)
{
        //1.socket
        int s_fd;
        int c_fd;
        char Readbuf[128];
        int n_read;
        char msg[128]={0};
        //      char *msg ="I get you connet";
        struct sockaddr_in s_addr;
        struct sockaddr_in c_addr;

        if(argc != 3){
                printf("progam is wrong\n");
        }


        memset(&s_addr,0,sizeof(struct sockaddr_in));
        memset(&c_addr,0,sizeof(struct sockaddr_in));

        s_fd=socket(AF_INET,SOCK_STREAM,0);
        if(s_fd == -1){
                perror("socket");
                exit(-1);
        }
        s_addr.sin_family=AF_INET;
        s_addr.sin_port=htons(atoi(argv[2]));//端口号需要用htons函数进行转换
        inet_aton(argv[1],&s_addr.sin_addr);//地址装换api 

        //2.bind
        bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
        //3.listen
        listen(s_fd,10);
        while(1){
                //4.accept
              int clen = sizeof(struct sockaddr_in);
                c_fd =accept(s_fd,(struct sockaddr *)&c_addr,&clen);
                if(c_fd == -1){
                        perror("accept");
                }
                printf("get connect is %s \n",inet_ntoa(c_addr.sin_addr));//把网络格式的IP地址装>换成字符串形式
                //5.read
                if(fork()==0){
                        if(fork()==0){
                                while(1){
                                        printf("sever input:");
                                        memset(msg,0,sizeof(msg));
                                        gets(msg);
                                        write(c_fd,msg,strlen(msg));
                                }
                        }
                        while(1){
                                memset(Readbuf,0,sizeof(Readbuf));
                                n_read=read(c_fd,Readbuf,128);
                                if(n_read == -1){
                                        perror("read");
                                }
                                else{
                                        if(n_read==0){
                                                exit(0);
                                        }
                                        printf("the message is from clint: %d,%s\n",n_read,Readbuf);
                                }
                        }

                        //6.write               
                        break;
                }
        }
        return 0;
}

客户端代码 clint.c

#include<stdio.h>
#include<stdlib.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
//#include<linux/in.h> 与netinet/in.h冲突:了
#include <arpa/inet.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include<string.h>

int main(int argc, char **argv)
{
        //1.socket
        int c_fd;
        char Readbuf[128];
        int n_read;
        char msg[128]={0};
        //char *msg ="the message is from clint233";
        struct sockaddr_in c_addr;

        if(argc != 3){
                printf("progam is wrong\n");
        }

        memset(&c_addr,0,sizeof(struct sockaddr_in));

        c_fd=socket(AF_INET,SOCK_STREAM,0);
        if(c_fd == -1){
                perror("socket");
                exit(-1);
        }
        c_addr.sin_family=AF_INET;
        c_addr.sin_port=htons(atoi(argv[2]));//端口号需要用htons函数进行转换
        inet_aton(argv[1],&c_addr.sin_addr);//地址装换api 
        //connect
        if(connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr_in))==-1){
                perror("connect");
                exit(-1);
        }
        while(1){

                if(fork()==0){
                        while(1){
                                printf("input:");
                                memset(msg,0,sizeof(msg));
                               gets(msg);
                                write(c_fd,msg,strlen(msg));
                        }
                }
                while(1)
                {
                        memset(Readbuf,0,sizeof(Readbuf));
                        n_read=read(c_fd,Readbuf,128);
                        if(n_read == -1){
                                perror("read");
                        }
                        else{
                                if(n_read==0){
                                        printf("\n");
                                        exit(0);
                                }
                                printf("the message is from sever  %d,%s\n",n_read,Readbuf);
                        }
                }
                break;
        }

        return 0;
}

结果示例:
Liunx系统编程之网络编程——socket

上一篇:网络编程:实现计算器程序的服务器端/客户端(包含+ - * / %)


下一篇:ASP.NET MVC 学习8、Controller中的Detail和Delete方法