UDP和TCP的学习总结

首先是要了解两个概念
1 套接字的函数
socket函数
该函数的功能是创建一个套接字。
该函数的原型如下:
SOCKET socket ( int af,int type, int protocol );
af:表示一个地址家族,通常为AF_INET(ipv4地址)。AF_INET6(ipv6地址) 跟PF_INET 一样的 PF_INET6
tpe:表示套接字类型,如果为SOCK_STREAM,表示创建面向连接的流式套接字(TCP);为SOCK_DGRAM(UDP),表示创建面向无连接的数据报套接字;为SOCK_RAW,表示创建原始套节字。对于这些值,用户可以在Winsock2.h头文件中找到。
potocol:表示套接口所用的协议,如果用户不指定,可以设置为0。
返回值:创建的套接字句柄。
失败:返回-1

bind函数 :都是绑定自己的ip地址
将创建的套接字绑定(bind)到本地的地址和端口上
绑定一个套接字
一般来说,与客户端的套接字关联的地址没有太大意义,可以让系统默认的地址,
而对服务端需要给一个接收客户端请求的套接字绑定一个众所周知的地址.
int bind(int sockfd,const struct *addr,socklen_t len);
返回值:成功 0
失败 -1
参数:sockfd:套接字的文件描述符
const struct *addr: 存放你要绑定的ip和端口号

       struct sockaddr --》通用地址结构体,兼容ipv4 和ipv6
       struct sockaddr_in  --》ipv4地址结构体
       struct sockaddr_in6   --》ipv6地址结构体
      
        //绑定:使sockFd工作在固定的port 或 IP
        struct sockaddr_in  serAddr;//定义ipv4地址结构体
        serAddr.sin_family=AF_INET;//地址协议类型 ipv4
        serAddr.sin_port  =htons(SER_PORT);//端口号:(注意网络字节序)  主机h   to(转换)   n(网络)  l(长整形)  n(短整型)    
        //serAddr.sin_addr.s_addr=inet_addr(SER_IP);//字符串转换为网络地址 将主机字节序ip-->>网络字节序    也就是小端序变大端序
        serAddr.sin_addr.s_addr=INADDR_ANY;  //指本主机的所有IP 包括广播组播的IP
        //inet_ntoa(); 网络地址转换为字符串
        memset(serAddr.sin_zero,0,8);//bzero(serAddr.sin_zero,8);

.在进程运行的机器上,指定的地址必须有效,不能指定一个其他机器的地址
.地址必须和创建套接字时的地址族所支持的格式相匹配
.端口号不小于1024(1024以内的大多是系统的所以不小于1024),超级用户除外

listen函数:
该函数的功能是将套接字设置为监听模式。对于流式套接字,必须处于监听模式才能够接收客户端套接字的连接。
该函数的原型如下:
int listen ( SOCKET s, int backlog);
参数说明:
s:表示套接字标识。
backlog:表示等待连接的最大队列长度。例如,如果backlog被设置为2,此时有3个客户端同时发出连接请求,那么前2个客户端连接会放置在等待队列中,第3个客户端会得到错误信息。

accpet函数:
该函数的功能是接受客户端的连接。在流式套接字中,只有在套接字处于监听状态,才能接受客户端的连接。
该函数的原型如下:
SOCKET accept ( SOCKET s, struct sockaddr FAR* addr, int FAR* addrlen );
参数说明:
s:是一个套接字,它应处于监听状态。
addr:是一个sockaddr_in结构指针,包含一组客户端的端口号、IP地址等信息。
addrlen:用于接收参数addr的长度。
返回值:一个新的套接字,它对应于已经接受的客户端连接,对于该客户端的所有后续操作,都应使用这个新的套接字。

closesocket函数:
该函数的功能是关闭套接字。
该函数的原型如下:
int closesocket (SOCKET s);
参数说明:
s:标识一个套接字。如果参数s设置了SO_DONTLINGER选项,则调用该函数后会立即返回,但此时如果有数据尚未传送完毕,则会继续传递数据,然后才关闭套接字。

connect函数:
该函数的功能是发送一个连接请求。
该函数的原型如下:
int connect (SOCKET s,const struct sockaddr FAR* name,int namelen );
参数说明:
s:表示一个套接字。
name:表示套接字s要连接的主机地址和端口号。
namelen:是name缓冲区的长度。
返回值:如果函数执行成功,返回值为0,否则为SOCKET_ERROR。用户可以通过WSAGETLASTERROR得到其错误描述。

htons函数:
该函数的功能是将一个16位的无符号短整型数据由主机排列方式转换为网络排列方式。
该函数的原型如下:
uint32_t htonl(uint32_t hostlong);
参数说明:
hostshort:是一个主机排列方式的无符号短整型数据。
返回值:函数返回值是16位的网络排列方式数据。

htonl函数:
该函数的功能是将一个无符号长整型数据由主机排列方式转换为网络排列方式。
该函数的原型如下:
uint32_t htonl(uint32_t hostlong);
参数说明:
hostlong:表示一个主机排列方式的无符号长整型数据。
返回值:32位的网络排列方式数据。

inet_addr函数:
该函数的功能是将一个由字符串表示的地址转换为32位的无符号长整型数据。
该函数的原型如下:
unsigned long inet_addr (const char FAR * cp);
in_addr_t inet_addr(const char *cp);
参数说明:
cp:表示一个IP地址的字符串。
返回值:32位无符号长整数。

大端ip—>小端ip src 英文:原 dst 英文:目标
const char *inet_ntop(int af, const void *src,char *dst, socklen_t size);

char *inet_ntoa(struct in_addr in);

端口:大端—>小端
uint32_t ntohl(uint32_t netlong);
32位 4字节

uint16_t ntohs(uint16_t netshort);
16位 2字节


recv函数:
该函数的功能是从面向连接的套接字中接收数据。
该函数的原型如下:
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
参数说明:
s:表示一个套接字。
buf:表示接收数据的缓冲区。
len:表示buf的长度。
flags:表示函数的调用方式。如果为MSG_PEEK,则表示查看传来的数据,在序列前端的数据会被复制一份到返回缓冲区中,但是这个数据不会从序列中移走;如果为MSG_OOB,则表示用来处理Out-Of-Band数据,也就是外带数据。

send函数:
该函数的功能是在面向连接方式的套接字间发送数据。
该函数的原型如下:
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
参数说明:
s:表示一个套接字。
buf:表示存放要发送数据的缓冲区。
len:表示缓冲区长度。
flags:表示函数的调用方式。

recvfrom函数:
该函数用于接收一个数据报信息并保存源地址。
该函数的原型如下:
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
参数说明:
sockfd:表示准备接收数据的套接字。
buf:指向缓冲区的指针,用来接收数据。
len:表示缓冲区的长度。
flags:通过设置这个值可以影响函数调用的行为。
src_addr:是一个指向地址结构的指针,用来接收发送数据方的地址信息。
addrlen:表示缓冲区的长度。

sendto函数:
该函数的功能是向一个特定的目的方发送数据。
该函数的原型如下:
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
sockfd:表示一个(可能已经建立连接的)套接字的标识符。
buf:指向缓冲区的指针,该缓冲区包含将要发送的数据。
len:表示缓冲区的长度。
flags:通过设置这个值可以影响函数调用的行为。
dest_addr:指定目标套接字的地址。
addrlen:表示缓冲区的长度。

返回值:

WSACleanup函数:
该函数的功能是释放为Ws2_32.dll动态链接库初始化时分配的资源。
该函数的原型如下:
int WSACleanup (void);
使用该函数关闭动态链接库:
WSACleanup(); /关闭动态链接库/

不再多说举个例子吧
基于TCP的网络聊天程序
服务端
按照以下顺序编写代码:
(1)创建套接字。
(2)绑定套接字到本地的地址和端口上。
(3)设置套接字为监听状态。
(4)接受请求连接的请求。
(5)进行通信。
(6)通信完毕,释放套接字资源。

客户端
按照以下顺序编写代码:
(1)创建套接字。
(2)发出连接请求。
(3)请求连接后进行通信操作。
(4)释放套接字资源。

通信之间都会有两个服务进行通信,一个客户端一个服务接收端
服务端接收
建立套接字
//1 建立套接
sockfd=socket(AF_INET,SOCK_STREAM,0);
//2 绑定一个套接字 就是使一个sockfd工作在一个固定的port或IP地址上
struct sockaddr_in serAddr;
serAddr.sin_family=AF_INET;//地址协议类型
serAddr.sin_port =htons(SER_PORT);//端口号:(注意网络字节序)
serAddr.sin_addr.s_addr=INADDR_ANY; //指本主机的所有IP 包括广播组播的IP
//inet_ntoa(); 网络地址转换为字符串
memset(serAddr.sin_zero,0,8);//bzero(serAddr.sin_zero,8);

//将创建的套接字绑定(bind)到本地的地址和端口上 
/*
绑定一个套接字
一般来说,与客户端的套接字关联的地址没有太大意义,可以让系统默认的地址,
而对服务端需要给一个接收客户端请求的套接字绑定一个众所周知的地址.
int bind(int sockfd,const struct *addr,socklen_t len);
.在进程运行的机器上,指定的地址必须有效,不能指定一个其他机器的地址
.地址必须和创建套接字时的地址族所支持的格式相匹配
.端口号不小于1024,超级用户除外**/

ret=bind(sockfd,(struct sockaddr *)&serAddr,sizeof(serAddr));
if(ret==-1)
{
    perror("bind");
    shutdown(sockfd,SHUT_RDWR);
    return 2;
}
    //监听:MAX_WAIT最大的半链接等待队列大小 设置套接字的状态为监听状态(listen),准备接受客户端的连接请求。 
//服务器调listen宣告可接受连接请求
//int listen(int sockfd,int backlog);   backlog :支持连接数
if(listen(sockfd,MAX_WAIT)==-1)
{
    perror("listen");
    shutdown(sockfd,SHUT_RDWR);
    return 3;
}
printf("服务器启动完成.\n");
//处理链接
int newFd;
char *erMsg="Sorry,Server Busy!";
pid_t  pid;
struct sockaddr_in  peer;//记录对方的信息
socklen_t  len=sizeof(peer);

while(1)
{
   /*
    * 没有连接到来阻塞 如果连接到来则处理 成功返回建立连接的新套接字
   */
   /*服务器调listen之后则可能调用accept建立连接
	int accept(int sockfd,struct addr *peer_addr,socklen_t *len);
	成功返回用以交换数据的套接字,该套接字连接到调用connect的客户端
	peer_addr 获取连接方的标识信息,如果不关心可填NULL
	如果没有连接请求时,accept阻塞到一个请求的到来*/
	
   //newFd=accept(sockfd,NULL,NULL);
   newFd=accept(sockfd,(struct sockaddr *)&peer,&len);
   if(newFd<0)  continue;//error
   //inet_addr()字符串转换为网络地址 inet_ntoa()网络地址转换为字符串; 网络地址转换为字符串 
   //htons();端口号:(注意网络字节序)由主机排列方式转换为网络排列方式 ntohs()由网络排列方式转换为主机排列方式
   printf("from:%s<%d>\n",inet_ntoa(peer.sin_addr),ntohs(peer.sin_port));
   //建立进程或线程对newFd服务 sockfd继续监听
   pid=fork();//产生子进程
   if(pid==-1)//错误
   {
      write(newFd,erMsg,strlen(erMsg));
      shutdown(newFd,SHUT_RDWR);//关闭读写端
      continue;
   }
   else if(pid==0)//child
   {
      doWork(newFd);//连接一个客户端 就创建就创建子进程 对其进行操作
      shutdown(newFd,SHUT_RDWR);//关闭读写端
      exit(0);
   }
}
以上是TCP的一个完整的聊天程序
接下来就是一个客户端的

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>

#include <arpa/inet.h>//for htons
#include <netinet/in.h>//inet_addr inet_ntoa

#include <signal.h>

#define SER_IP “192.168.2.112”
#define SER_PORT 10086

#define SIZE 1024

int main(void)
{
int sockfd,ret;

//建立套接
sockfd=socket(AF_INET,SOCK_STREAM,0);//PF_INET/AF_INET  IPV4因特网域 SOCK_STREAM 有序 可靠 双向的面向连接的字节流 (默认协议TCP/IP) protocol:可以通过该参数指定选择一个特定的协议 ,一般填为0 使用默认的协议
if(sockfd==-1)
{
   perror("create socket");
   return 1;
}
printf("sockfd:%d\n",sockfd);
//连接:初始要连接的对象信息
struct sockaddr_in  serAddr;
serAddr.sin_family=AF_INET;//地址协议类型
serAddr.sin_port  =htons(SER_PORT);//端口号:(注意网络字节序)
serAddr.sin_addr.s_addr=inet_addr(SER_IP);//字符串转换为网络地址
//inet_ntoa(); 网络地址转换为字符串
memset(serAddr.sin_zero,0,8);//bzero(serAddr.sin_zero,8);

//如果处理的是面向连接的网络服务(SOCK_STREAM 或 SOCK_SEQAPCKET)在交换数据以前,建立一个连接    向服务器发出连接请求(connect)。
// int connect(int sockfd,const struct sockaddr *perr_addr,socklen_t len);
ret=connect(sockfd,(struct sockaddr *)&serAddr,sizeof(serAddr));
if(ret==-1)
{
    perror("connect");
    shutdown(sockfd,SHUT_RDWR);//how:SHUT_RD   关闭读写端 
    return 2;
}
//
char buf[SIZE+1];
char str[]="hello";
while(1)
{
    
    ret=read(sockfd,buf,SIZE);//阻塞  读取数据
    if(ret==0)  break;//distconnect
    else if(ret==-1)
    {
        perror("read error");
        break;
    }
    buf[ret]='\0';
    printf("%s\n",buf);
}

shutdown(sockfd,SHUT_RDWR);//close(sockfd);关闭读写端
return 0;

}
大致就是这么操作的有疑问可以提出来哈哈

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章