初探socket

现在很多公司在招聘员工的时候都需要掌握socket编程,利用一个闲暇的周末来了解一下socket,由于是为了面试给自己加分,所以学的比较浅,所以只是初探,比较适合跟我一样的菜鸟。


———————————————————————————————————————————————————————————————————————

首先我先找到了网上一份代码,通过对这个代码的实现来理解socket。


这两个分别是服务器端的执行文件和客户端的执行文件,当你看到有这2个文件的时候,脑袋中应该就有一个想法了socket是用来干嘛的?这样一看就知道了就是客户端和服务器端互相传输数据的嘛


然后就是执行,刚一开始的时候我都不知道如何执行这个文件,因为运行服务器的这个文件的时候一直停着不动。


后来我才明白,如果你想在你本机上实现socket传输的话,你就还得打开一个终端:


一个终端执行服务器文件,另一个终端执行客户端的文件:

clint:


server:



大家可以看到我在执行客户端的时候后面接了ip地址,这个地址是我这个主机的ip地址,因为我的服务器是放在我这个主机上的,如果你的服务器放在别人电脑上,只要在同一个局域网内,那么你就可以通过socket客户端发送和他的服务器端互相交流来了,这个已经试验成功了。


大家通过这个程序应该能大致了解了socket是干嘛的吧,我的理解就是网络通信的一种方式,因为socket的地址组成部分是:ip地址+端口号,ip地址就是对方的电脑在局域网中的位置,首先通过ip地址定位到是与哪台电脑进行通信,然后通过端口号确定是这个电脑中的哪个服务程序,因为电脑中有很多的服务程序,比如你要上网,其实就是访问别人的电脑里面的文件,只不过规定了访问http这个服务的端口号80,这里我们的端口号是自己设置的一个(最好自己设置一个,如果系统跟你分配一个的话,怕重复)来与别人电脑通信。


贴一下网上别人的理解:

把Socket看作邮递员,把IP地址看作收信人的地址,端口号看作收信人的门牌号。我们的电脑同时可以运行多个程序,也就是有多个socket,那么当电脑收到很多个网络消息的时候怎么区分到底是给哪个程序的信息呢? 就是通过不同的端口号来区分。这就好像一个邮递员拿着一大袋子新建来到一个小区门口,他想准确的把每封信件送到正确的人手中的话,就需要知道收件人的门牌号。端口就是这个作用。


以上就是我运行这个程序然后对于socket的理解,下面我就具体通过这个代码来说一下socket编程

———————————————————————————————————————————————————————————————————————

首先我们是需要了解2个结构体,你可以看socket代码中这2个是必出现的:

struct sockaddr


unsigned short sa_family; /* 地址族, AF_xxx */ 
char sa_data[14]; /* 14字节的协议地址*/ 
}; 
上面是通用的socket地址,具体到Internet socket,用下面的结构,二者可以进行类型转换 

struct sockaddr_in 


short int sin_family; /* 地址族,AF_xxx 在socket编程中只能是AF_INET */ 
unsigned short int sin_port; /* 端口号 (使用网络字节顺序) */ 
struct in_addr sin_addr; /* 存储IP地址 4字节 */ 
unsigned char sin_zero[8]; /* 总共8个字节,实际上没有什么用,只是为了和struct sockaddr保持一样的长度 */ 
}; 


AF_INET:选择 AF_INET 的目的就是使用 IPv4 进行通信。因为 IPv4 使用 32 位地址,相比 IPv6 的 128 位来说,计算更快,便于用于局域网通信。


刚才我说了socket的地址就是:ip+端口

struct in_addr sin_addr;这个结构体里面就是放ip地址的

一般的定义是:

sockaddr_in    servaddr;


servaddr.sin_family = AF_INET;

servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//将我的ip地址进行转换

servaddr.sin_port = htons(6666);//指定端口为6666


====================================================================================================================================

作为服务器,你要绑定【bind】到本地的IP地址上进行监听【listen】,但是你的机器上可能有多块网卡,也就有多个IP地址,这时候你要选择绑定在哪个IP上面,如果指定为INADDR_ANY,那么系统将绑定默认的网卡【即IP地址】。

作为客户端,你要连接【connect】到远端的服务器,也是要指定远端服务器的(ip, port)对。
当然,在这种情况下,不可能将IP地址指定为INADDR_ANY,系统会疯掉的。

======================================================================================================

所以INADDR_ANY就是我们本地网卡的地址。

htonl(INADDR_ANY):将主机的无符号长整形数转换成网络字节顺序,例如我的主机IP是192.168.1.3,那么它将转换为32位数:0xC0A80103存储在我的这个结构体中

函数原型:

uint32_t htonl(uint32_t hostlong);  

hostlong:主机字节顺序表达的32位数。 

与它相对应的就是inet_addr()是将一个点分制的IP地址(如192.168.0.1)转换为上述结构中需要的32位IP地址(0xC0A80103),与htonl相反

htons是操作16位数的,用来操作端口号,将主机的无符号短整形数转换成网络字节顺序。我设置的是6666,转化后就为0xa1a


这些结构体了解完了,我们就需要了解一下socket编程中必备的函数:socket,blind,listen,connet,accept,send,recev.

socket

____________________________________________________________________________________________________________________________________

int socket(int domain,int type,int protocol);
第一个参数domain设置为“AF_INET”。//ipv4
第二个参数是套接口的类型:SOCK_STREAM或SOCK_DGRAM。//sock_stream是数据流,使用tcp协议,sock_dgram是数据报,udp协议

第三个参数设置为0。

系统调用socket()只返回一个套接口描述符,如果出错,则返回-1。

这个就是我们平时用的fd句柄一样,用来表示每个文件的类型和作用,通过这个句柄来访问其内部函数和属性


blind:

___________________________________________________________________________________________________________________________________________________________________________

int bind(int sockfd,struct sockaddr*my_addr,int addrlen);
第一个参数sockfd是由socket()调用返回的套接口文件描述符。
第二个参数my_addr是指向数据结构sockaddr的指针。数据结构sockaddr中包括了关于你的地址、端口和IP地址的信息。
第三个参数addrlen可以设置成sizeof(structsockaddr)。


这个函数就是将我们的ip地址和端口号绑定起来


将上面2个函数结合一下就是:

int listenfd;

listenfd = socket(AF_INET, SOCK_STREAM, 0);

bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr));//前面就说了struct sockaddr和struct sockaddr_in是可以互相转化的,不过必须要用这个方式

 (struct sockaddr*)&servaddr,因为形参是指针嘛


listen:

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

int listen(int sockfd,int backlog);
第一个参数是系统调用socket()返回的套接口文件描述符。

第二个参数是进入队列中允许的连接的个数。进入的连接请求在使用系统调用accept()应答之前要在进入队列中等待。这个值是队列中最多可以拥有的请求的数。大多数系统的缺省设置为20。你可以设置为5或者10。当出错时,listen()将会返回-1值。


connect:

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

int connect(int sockfd,struct sockaddr* serv_addr,int addrlen);

第一个参数还是套接口文件描述符,它是由系统调用socket()返回的。

第二个参数是serv_addr是指向数据结构sockaddr的指针,其中包括目的端口和IP地址。

第三个参数可以使用sizeof(struct sockaddr)而获得。


在TCP客户端,首先调用一个socket()函数,得到一个socket描述符sockefd,然后通过connect函数对服务器进行连接,连接成功后,就可以利用这个sockefd描述符使用send/recv函数收发数据了


这个是由客户端发送的,因为我们用的是tcp协议,所以通信的时候不得不进过3次握手,而调用这个函数就直接进行了3次握手,具体如何实现的我们不用去了解,内核里面实现的,我们只需要调用就行。

======================================================================================================

实际上可分为4

客户端发起connect(),发送SYN j

服务器从SYN queue中建立条目,响应SYN k, ACK J+1

客户端connect()成功返回,响应ACK K+1

服务器将socketSYN queue移入accept queueaccept()成功返回



======================================================================================================



accept:

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

系统调用accept()比较起来有点复杂。在远程的主机可能试图使用connect()连接你使用listen()正在监听的端口。但此连接将会在队列中等待,直到使用accept()处理它。调用accept()之后,将会返回一个全新的套接口文件描述符来处理这个单个的连接。这样,对于同一个连接来说,你就有了两个文件描述符。原先的一个文件描述符正在监听你指定的端口,新的文件描述符可以用来调用send()和recv()。
调用的例子如下:
#include<sys/socket.h>
int  accept(intsockfd,void*addr,int*addrlen);
第一个参数是正在监听端口的套接口文件描述符。第二个参数addr是指向本地的数据结构sockaddr_in的指针。调用connect()中的信息将存储在这里。通过它你可以了解哪个主机在哪个端口呼叫你。第三个参数同样可以使用sizeof(structsockaddr_in)来获得。如果出错,accept()也将返回-1。

===================================================================================================================

这个函数应该是目前我觉得最难理解的,因为它返回的值是一个新的socket文件描述符。

我们一开始就创建了一个socket文件描述符,然后也绑定了ip和端口号,现在又出来一个新的文件描述符,从代码中对于数据的传送是用的那个新的socket文件描述符

connfd = accept(listenfd, (struct sockaddr*)NULL, NULL);//listenfd是我一开始创建的描述符,connfd是accept返回的文件描述符,这里我没有再设一个新的结构体来保存传来的socket的信息,所以第2位和第3位设置的是NULL;

n = recv(connfd, buff, MAXLINE, 0);

目前我对这个问题的理解是:客户端调用connect这个函数的时候,内核里面就将这个客户端的socket描述符进行共享了,然后服务端调用accept的时候就会返回这个socket描述符,服务端再通过调用这个描述符来进行数据(send recv)的传输任务,最开始的创建的描述符是用来监听的。

======================================================================================================


如果我想打印出客户端的信息的话,我就需要再申请一个同样的结构体,利用accept函数将客户端传来的信息保存在这个结构体中,不过对于代码的修改需要注意一下:

如果你是这样做的话就会出现错误:

sockaddr_in c_addr;

int len;

while(1)

{

 .....

len =sizoef(c_addr);

 connfd = accept(listenfd, (struct sockaddr*)&c_addr, len);

}


找到这个函数定义的地方:原来accept是这样定义的:

extern int accept (int __fd, __SOCKADDR_ARG __addr,  socklen_t*__restrict __addr_len);// 这里很容易出错,不注意的话按以前的习惯总是会出错的

我们需要传地址,所以应该修改成:

 connfd = accept(listenfd, (struct sockaddr*)&c_addr, &len)


send:

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

int send(int sockfd,const void* msg,int len,int flags);
第一个参数是你希望给发送数据的套接口文件描述符。它可以是你通过socket()系统调用返回的,也可以是通过accept()系统调用得到的。
第二个参数是指向你希望发送的数据的指针。
第三个参数是数据的字节长度。第四个参数标志设置为0。


recv:

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

int recv(int sockfd,void* buf,int len,unsigned int flags);
第一个参数是要读取的套接口文件描述符。
第二个参数是保存读入信息的地址。
第三个参数是缓冲区的最大长度。第四个参数设置为0。
系统调用recv()返回实际读取到缓冲区的字节数,如果出错则返回-1。
这样使用上面的系统调用,你可以通过数据流套接口来发送和接受信息。


如果客户端中断了则返回0,,这个条件就是我判断对方是否退出的依据。


———————————————————————————————————————————————————————————————————————

目前实现了同一个网段内不同主机进行网络通信,不过只是很简单的刚开始,后面想通过对别人收发信息的socket的程序再来了解一下socket编程。

下面还是附上代码:

服务端:

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

#define MAXLINE 4096

int main(int argc, char** argv)
{
    int    listenfd, connfd;
    struct sockaddr_in     servaddr;
    char    buff[4096];
    int     n;

    if( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1 ){
    printf("create socket error: %s(errno: %d)\n",strerror(errno),errno);
    exit(0);
    }

    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(6666);

    if( bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1){
    printf("bind socket error: %s(errno: %d)\n",strerror(errno),errno);
    exit(0);
    }

    if( listen(listenfd, 10) == -1){
    printf("listen socket error: %s(errno: %d)\n",strerror(errno),errno);
    exit(0);
    }

    printf("======waiting for client's request======\n");
    while(1){
    if( (connfd = accept(listenfd, (struct sockaddr*)NULL, NULL)) == -1){
        printf("accept socket error: %s(errno: %d)",strerror(errno),errno);
        continue;
    }
    n = recv(connfd, buff, MAXLINE, 0);
    buff[n] = '\0';
    printf("recv msg from client: %s\n", buff);
    close(connfd);
    }

    close(listenfd);
}

客户端:

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

#define MAXLINE 4096

int main(int argc, char** argv)
{
    int    sockfd, n;
    char    recvline[4096], sendline[4096];
    struct sockaddr_in    servaddr;

    if( argc != 2){
    printf("usage: ./client <ipaddress>\n");
    exit(0);
    }

    if( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
    printf("create socket error: %s(errno: %d)\n", strerror(errno),errno);
    exit(0);
    }

    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(6666);
    if( inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0){
    printf("inet_pton error for %s\n",argv[1]);
    exit(0);
    }

    if( connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0){
    printf("connect error: %s(errno: %d)\n",strerror(errno),errno);
    exit(0);
    }

    printf("send msg to server: \n");
    fgets(sendline, 4096, stdin);
    if( send(sockfd, sendline, strlen(sendline), 0) < 0)
    {
    printf("send msg error: %s(errno: %d)\n", strerror(errno), errno);
    exit(0);
    }

    close(sockfd);
    exit(0);
}






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值